Using QFileDialog in Custom Delegate displayed on the wrong screen
-
I have a app were i want to edit a displayed picture in a QTableView. To achieve the edit i used a custom delegate for that row. Here is the code:
#include "picturedelegate.h" #include <QLabel> #include <QBuffer> #include <QFileDialog> #include <QStandardPaths> PictureDelegate::PictureDelegate(QObject *parent) :QStyledItemDelegate{ parent } { } QWidget *PictureDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto editor = new QFileDialog{ parent }; editor->setNameFilter(tr("Images (*.png)")); auto startDir = QStandardPaths::standardLocations( QStandardPaths::DesktopLocation); editor->setDirectory(startDir[0]); editor->setFileMode(QFileDialog::ExistingFile); return editor; } void PictureDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { auto fileDialog = static_cast<QFileDialog *>(editor); QStringList fileNames; fileNames = fileDialog->selectedFiles(); if(!fileNames.isEmpty()) { QPixmap pixmap; pixmap.load(fileNames[0]); QByteArray bArray; QBuffer buffer(&bArray); buffer.open(QIODevice::WriteOnly); pixmap.save(&buffer, "PNG"); model->setData(index, bArray, Qt::EditRole); } }
Everything seems to work i can update pictures into my model.
The only issue if have is that if i click on the picture in the table the FileDialog does not spawn near my application.
I have the appilcation on my primary screen and click in the table.
The File Dialog shows up on the other screen far far away.What could be the cause. Ist it possible to spawn it over the selected element in the table?
-
Hi,
What version if Qt are you using ?
What OS are you running ?
If Linux, what desktop environment are you using ? -
QT 5.12.4
KDE Neon
KDE Plasma -
It indeed seems to be an OS Issue. I run the same application in an windows 10 environment and there it perfectly fine displays the dialog over the view i just clicked.
-
You are likely correct.
What version of Neon/Plasma are you running ?
-
KDE neon User Edition 5.16
-
Can you provide a minimal compilable example that triggers this ?
-
I tryed to strip down my program to the Issue. Here is the code:
main.h
#include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication app{ argc, argv }; MainWindow window; window.show(); return QApplication::exec(); }
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class QuestionModel; class QTableView; class PictureDelegate; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); private: QuestionModel *mQuestionModel; PictureDelegate *mPictureDelegate; QTableView *mQuestionsView{}; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "questionmodel.h" #include "picturedelegate.h" #include <QTableView> #include <QGuiApplication> #include <QScreen> #include <QHeaderView> #include <QTimer> MainWindow::MainWindow() :mQuestionModel{ new QuestionModel }, mPictureDelegate{ new PictureDelegate } { Question q; mQuestionModel->addQuestion(q); setFixedSize(QGuiApplication::primaryScreen()->availableGeometry().size() * 0.6); mQuestionsView = new QTableView; mQuestionsView->setModel(mQuestionModel); mQuestionsView->setItemDelegateForColumn(1, mPictureDelegate); mQuestionsView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred); mQuestionsView->setWordWrap(true); QTimer::singleShot(10, this, [this]() { mQuestionsView->verticalHeader()->setSectionResizeMode( QHeaderView::ResizeToContents); } ); mQuestionsView->verticalHeader() ->setSectionResizeMode(QHeaderView::ResizeToContents); mQuestionsView->setColumnHidden(0, true); mQuestionsView->horizontalHeader() ->setSectionResizeMode( 1, QHeaderView::Stretch ); setCentralWidget(mQuestionsView); }
question.h
#ifndef QUESTION_H #define QUESTION_H #include <QString> class Question { public: Question(int id, QByteArray picture = QByteArray{}) : mId{id}, mPicture{picture} { } Question(QByteArray picture = QByteArray{}) :Question{-1, picture} { } int getId() const { return mId; } void setId(int id) { mId = id; } QByteArray getPicture() const { return mPicture; } void setPicture(const QByteArray &picture) { mPicture = picture; } private: int mId{ -1 }; QByteArray mPicture; }; #endif // QUESTION_H
questiondao.h
#ifndef QUESTIONDAO_H #define QUESTIONDAO_H #include <QVector> #include <vector> #include <memory> class QSqlDatabase; class Question; class QuestionDao { public: explicit QuestionDao(QSqlDatabase &database); void init() const; void addQuestion(Question &question) const; void updateQuestion(Question &question) const; std::unique_ptr<std::vector<std::unique_ptr<Question>>> getAllQuestions() const; private: QSqlDatabase &mDatabase; }; #endif // QUESTIONDAO_H
questiondao.cpp
#include "questiondao.h" #include "question.h" #include "databasemanager.h" #include <QSqlDatabase> #include <QSqlQuery> #include <QStringList> #include <QVariant> QuestionDao::QuestionDao(QSqlDatabase &database) :mDatabase{ database } { } void QuestionDao::init() const { if(!mDatabase.tables().contains("questions")){ QSqlQuery query{ mDatabase }; query.exec( "CREATE TABLE questions (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "picture BLOB)"); DatabaseManager::debugQuery(query); } } void QuestionDao::addQuestion(Question &question) const { QSqlQuery query{ mDatabase }; query.prepare("INSERT INTO questions (picture) VALUES (:picture)"); query.bindValue(":picture", question.getPicture()); query.exec(); DatabaseManager::debugQuery(query); question.setId(query.lastInsertId().toInt()); } void QuestionDao::updateQuestion(Question &question) const { QSqlQuery query{ mDatabase }; if(!question.getPicture().isEmpty()) { query.prepare("UPDATE questions SET " "picture=:picture WHERE id=:id"); query.bindValue(":id", question.getId()); query.bindValue(":picture", question.getPicture()); query.exec(); DatabaseManager::debugQuery(query); } } std::unique_ptr<std::vector<std::unique_ptr<Question>>> QuestionDao::getAllQuestions() const { QSqlQuery query{ mDatabase }; query.prepare("SELECT * FROM questions"); query.exec(); DatabaseManager::debugQuery(query); auto list = std::make_unique<std::vector<std::unique_ptr<Question>>>(); while(query.next()) { auto id = query.value(0).toInt(); QByteArray picture; if(!query.value(1).isNull()) { picture = query.value(1).toByteArray(); } auto question = std::make_unique<Question>(id, picture); list->push_back(std::move(question)); } return list; }
databasemanager.h
#ifndef DATABASEMANAGER_H #define DATABASEMANAGER_H #include "questiondao.h" #include <QSqlDatabase> class QSqlError; class DatabaseManager { public: static void debugQuery(const QSqlQuery& query); static DatabaseManager &instance(); ~DatabaseManager(); DatabaseManager (const DatabaseManager &) = delete; DatabaseManager (DatabaseManager &&) = delete; DatabaseManager& operator=(const DatabaseManager &) = delete; DatabaseManager& operator=(DatabaseManager &&) = delete; protected: DatabaseManager(const QString &database_filename = default_filename); private: static constexpr auto default_filename = "quiz.db"; QString createPath(const QString &database_filename); QSqlDatabase *mDatabase; public: const QuestionDao questionDao; }; #endif // DATABASEMANAGER_H
#include "databasemanager.h" #include <QDir> #include <QDebug> #include <QFile> #include <QSqlError> #include <QSqlQuery> #include <QStandardPaths> void DatabaseManager::debugQuery(const QSqlQuery &query) { if (query.lastError().type() == QSqlError::ErrorType::NoError) { qDebug() << "Query OK:" << query.lastQuery(); qDebug() << "------"; } else { qWarning() << "Query KO:" << query.lastError().text(); qWarning() << "Query text:" << query.lastQuery(); qWarning() << "------"; } } DatabaseManager &DatabaseManager::instance() { static DatabaseManager singleton; return singleton; } DatabaseManager::~DatabaseManager() { mDatabase->close(); delete mDatabase; } DatabaseManager::DatabaseManager(const QString &database_filename) :mDatabase{ new QSqlDatabase{ QSqlDatabase::addDatabase("QSQLITE") } }, questionDao{ *mDatabase } { mDatabase->setDatabaseName(createPath(database_filename)); mDatabase->open(); questionDao.init(); } QString DatabaseManager::createPath(const QString &database_filename) { QString path{ QStandardPaths::writableLocation( QStandardPaths::StandardLocation::DesktopLocation) }; qDebug() << path; path.append(QDir::separator()).append(database_filename); return QDir::toNativeSeparators(path); }
questionmodel.h
#ifndef QUESTIONMODEL_H #define QUESTIONMODEL_H #include "question.h" #include <QAbstractTableModel> #include <QVector> #include <memory> #include <vector> class DatabaseManager; class QuestionModel : public QAbstractTableModel { Q_OBJECT public: explicit QuestionModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData( const QModelIndex &index, const QVariant &value, int role) override; QVariant headerData( int section, Qt::Orientation orientation, int role) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; public slots: void addQuestion(const Question &question); private: enum Column{ id, picture }; bool isIndexValid(const QModelIndex& index) const; DatabaseManager &mDb; std::unique_ptr<std::vector<std::unique_ptr<Question>>> mQuestions; }; #endif // QUESTIONMODEL_H
questionmodel.cpp
#include "questionmodel.h" #include "databasemanager.h" #include <QModelIndex> #include <QColor> #include <QPixmap> #include <QFileDialog> #include <QStandardPaths> QuestionModel::QuestionModel(QObject *parent) :QAbstractTableModel{ parent }, mDb(DatabaseManager::instance()), mQuestions(mDb.questionDao.getAllQuestions()) { } int QuestionModel::rowCount(const QModelIndex & /*parent*/) const { return static_cast<int>(mQuestions->size()); } int QuestionModel::columnCount(const QModelIndex & /*parent*/) const { return 2; } QVariant QuestionModel::data(const QModelIndex &index, int role) const { if (!isIndexValid(index)) { return QVariant(); } const Question &question = *mQuestions->at( static_cast<std::vector<std::unique_ptr<Question>>::size_type>( index.row())); if (role == Qt::DisplayRole) { if (index.column() == Column::id) { return question.getId(); } } if(index.column() == Column::picture) { QPixmap pixmap; if(!question.getPicture().isNull()) { pixmap.loadFromData(question.getPicture()); pixmap = pixmap.scaled( 100, 100, Qt::IgnoreAspectRatio, Qt::FastTransformation); } if (role == Qt::DecorationRole) { return pixmap; } if (role == Qt::SizeHintRole) { return pixmap.size(); } } if (role == Qt::EditRole) { if (index.column() == Column::picture) { return question.getPicture(); } } return QVariant{}; } bool QuestionModel::setData( const QModelIndex &index, const QVariant &value, int role) { if (value.toString().isNull()) { return false; } if (role == Qt::EditRole) { auto column = index.column(); Question &question = *mQuestions->at( static_cast<std::vector<std::unique_ptr<Question>>::size_type>( index.row())); auto editAccepted{ false }; if (column == Column::picture) { if(question.getPicture() != value.toByteArray()) { question.setPicture(value.toByteArray()); editAccepted = true; } } if(editAccepted) { mDb.questionDao.updateQuestion(question); return true; } } return false; } QVariant QuestionModel::headerData( int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if( orientation == Qt::Horizontal) { switch(section) { case Column::id: return tr("id"); case Column::picture: return tr("Picture"); } } } return QAbstractTableModel::headerData(section, orientation, role); } Qt::ItemFlags QuestionModel::flags(const QModelIndex &index) const { auto column = index.column(); if (column == Column::picture) { return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable; } return QAbstractTableModel::flags(index); } void QuestionModel::addQuestion(const Question &question) { Question q = question; mDb.questionDao.addQuestion(q); mQuestions = mDb.questionDao.getAllQuestions(); emit layoutChanged(); } bool QuestionModel::isIndexValid(const QModelIndex &index) const { return index.row() >= 0 && index.row() <rowCount() && index.isValid(); }
picturedelegate.h
#ifndef PICTUREDELEGATE_H #define PICTUREDELEGATE_H #include <QStyledItemDelegate> class PictureDelegate : public QStyledItemDelegate { public: explicit PictureDelegate(QObject *parent = nullptr); QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; }; #endif // PICTUREDELEGATE_H
picturedelegate.cpp
#include "picturedelegate.h" #include <QLabel> #include <QBuffer> #include <QFileDialog> #include <QStandardPaths> PictureDelegate::PictureDelegate(QObject *parent) :QStyledItemDelegate{ parent } { } QWidget *PictureDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const { auto editor = new QFileDialog{ parent }; editor->setNameFilter(tr("Images (*.png)")); auto startDir = QStandardPaths::standardLocations( QStandardPaths::DesktopLocation); editor->setDirectory(startDir[0]); editor->setFileMode(QFileDialog::ExistingFile); return editor; } void PictureDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { auto fileDialog = qobject_cast<QFileDialog *>(editor); QStringList fileNames; fileNames = fileDialog->selectedFiles(); if(!fileNames.isEmpty()) { QPixmap pixmap; pixmap.load(fileNames[0]); QByteArray bArray; QBuffer buffer(&bArray); buffer.open(QIODevice::WriteOnly); pixmap.save(&buffer, "PNG"); model->setData(index, bArray, Qt::EditRole); } }
This creates a Database with an entry on a desktop location. On startup click into the empty field and the popup should show up.
In my environment my Primary Screen has the app running but the popup shows up on my second screen.
-
I would say, there might be some patches applied to the Qt version shipped with KDE Neon. If you use the distribution provided Qt which is 5.12.3 at this time, the dialog opens as expected on the same screen.
I would recommend taking a look at the bug report system to see if there's anything related.