diff --git a/src/blackgui/models/listmodelbase.cpp b/src/blackgui/models/listmodelbase.cpp index aa740c7fc..42e8af59d 100644 --- a/src/blackgui/models/listmodelbase.cpp +++ b/src/blackgui/models/listmodelbase.cpp @@ -7,10 +7,17 @@ * contained in the LICENSE file. */ +// Drag and drop docu: +// http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views + #include "listmodelbase.h" #include "allmodelcontainers.h" +#include "blackgui/guiutility.h" #include "blackmisc/variant.h" +#include "blackmisc/json.h" #include "blackmisc/blackmiscfreefunctions.h" +#include +#include using namespace BlackMisc; @@ -27,10 +34,19 @@ namespace BlackGui QVariant CListModelBaseNonTemplate::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation != Qt::Horizontal) { return QVariant(); } + if (orientation != Qt::Horizontal) + { + return QVariant(); + } bool handled = (role == Qt::DisplayRole || role == Qt::ToolTipRole || role == Qt::InitialSortOrderRole); - if (!handled) {return QVariant();} - if (section < 0 || section >= this->m_columns.size()) { return QVariant(); } + if (!handled) + { + return QVariant(); + } + if (section < 0 || section >= this->m_columns.size()) + { + return QVariant(); + } if (role == Qt::DisplayRole) { @@ -90,13 +106,14 @@ namespace BlackGui if (!index.isValid()) { return f; } bool editable = this->m_columns.isEditable(index); f = editable ? (f | Qt::ItemIsEditable) : (f ^ Qt::ItemIsEditable); - const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); - if (formatter) - { - return formatter->flags(f, editable); - } - // fallback behaviour with no formatter + // flags from formatter + const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); + if (formatter) { f = formatter->flags(f, editable); } + + // drag and rop + f = f | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + return f; } @@ -105,11 +122,44 @@ namespace BlackGui return m_columns.getTranslationContext(); } + Qt::DropActions CListModelBaseNonTemplate::supportedDragActions() const + { + return Qt::CopyAction; + } + + Qt::DropActions CListModelBaseNonTemplate::supportedDropActions() const + { + return QAbstractItemModel::supportedDropActions(); + } + + QStringList CListModelBaseNonTemplate::mimeTypes() const + { + static const QStringList mimes( { "application/swift.container.json" }); + return mimes; + } + + void CListModelBaseNonTemplate::markDestroyed() + { + this->m_modelDestroyed = true; + } + + bool CListModelBaseNonTemplate::isModelDestroyed() + { + return m_modelDestroyed; + } + int CListModelBaseNonTemplate::ps_updateContainer(const CVariant &variant, bool sort) { return this->performUpdateContainer(variant, sort); } + CListModelBaseNonTemplate::CListModelBaseNonTemplate(const QString &translationContext, QObject *parent) + : QAbstractItemModel(parent), m_columns(translationContext), m_sortedColumn(-1), m_sortOrder(Qt::AscendingOrder) + { + // non unique default name, set translation context as default + this->setObjectName(translationContext); + } + template int CListModelBase::rowCount(const QModelIndex &parentIndex) const { @@ -144,14 +194,26 @@ namespace BlackGui bool CListModelBase::setData(const QModelIndex &index, const QVariant &value, int role) { Qt::ItemDataRole dataRole = static_cast(role); - if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole)) { return false; } + if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole)) + { + return false; + } // check / init - if (!this->isValidIndex(index)) { return false; } - if (!this->m_columns.isEditable(index)) { return false; } + if (!this->isValidIndex(index)) + { + return false; + } + if (!this->m_columns.isEditable(index)) + { + return false; + } const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); Q_ASSERT(formatter); - if (!formatter) { return false; } + if (!formatter) + { + return false; + } ObjectType obj = this->m_container[index.row()]; ObjectType currentObject(obj); @@ -175,6 +237,8 @@ namespace BlackGui template int CListModelBase::update(const ContainerType &container, bool sort) { + if (m_modelDestroyed) { return 0; } + // Keep sorting out of begin/end reset model ContainerType sortedContainer; int oldSize = this->m_container.size(); @@ -191,13 +255,17 @@ namespace BlackGui this->endResetModel(); int newSize = this->m_container.size(); - if (oldSize != newSize) { this->emitRowCountChanged(); } + if (oldSize != newSize) + { + this->emitRowCountChanged(); + } return newSize; } template void CListModelBase::update(const QModelIndex &index, const ObjectType &object) { + if (m_modelDestroyed) { return; } if (index.row() >= this->m_container.size()) { return; } this->m_container[index.row()] = object; @@ -216,6 +284,7 @@ namespace BlackGui CWorker *CListModelBase::updateAsync(const ContainerType &container, bool sort) { Q_UNUSED(sort); + if (m_modelDestroyed) { return nullptr; } auto sortColumn = this->getSortColumn(); auto sortOrder = this->getSortOrder(); CWorker *worker = BlackMisc::CWorker::fromTask(this, "ModelSort", [this, container, sortColumn, sortOrder]() @@ -224,6 +293,7 @@ namespace BlackGui }); worker->thenWithResult(this, [this](const ContainerType &sortedContainer) { + if (this->m_modelDestroyed) { return; } this->ps_updateContainer(CVariant::from(sortedContainer), false); }); worker->then(this, &CListModelBase::asyncUpdateFinished); @@ -233,6 +303,7 @@ namespace BlackGui template void CListModelBase::updateContainerMaybeAsync(const ContainerType &container, bool sort) { + if (m_modelDestroyed) { return; } if (container.size() > asyncThreshold && sort) { // larger container with sorting @@ -247,7 +318,7 @@ namespace BlackGui template bool CListModelBase::hasFilter() const { - return m_filter ? true : false; + return m_filter && m_filter->isValid() ? true : false; } template @@ -262,9 +333,13 @@ namespace BlackGui } template - void CListModelBase::setFilter(std::unique_ptr > &filter) + void CListModelBase::takeFilterOwnership(std::unique_ptr > &filter) { - if (!filter) { this->removeFilter(); return; } // empty filter + if (!filter) + { + this->removeFilter(); // empty filter + return; + } if (filter->isValid()) { this->m_filter = std::move(filter); @@ -371,7 +446,10 @@ namespace BlackGui template const ContainerType &CListModelBase::getContainerOrFilteredContainer() const { - if (!this->hasFilter()) { return this->m_container; } + if (!this->hasFilter()) + { + return this->m_container; + } return m_containerFiltered; } @@ -409,7 +487,10 @@ namespace BlackGui // new order this->m_sortedColumn = column; this->m_sortOrder = order; - if (this->m_container.size() < 2) { return; } // nothing to do + if (this->m_container.size() < 2) + { + return; // nothing to do + } // sort the values this->updateContainerMaybeAsync(this->m_container, true); @@ -419,7 +500,10 @@ namespace BlackGui void CListModelBase::truncate(int maxNumber, bool forceSort) { if (this->rowCount() <= maxNumber) { return; } - if (forceSort) { this->sort(); } // make sure container is sorted + if (forceSort) + { + this->sort(); // make sure container is sorted + } ContainerType container(this->getContainer()); container.truncate(maxNumber); this->updateContainerMaybeAsync(container, false); @@ -428,12 +512,19 @@ namespace BlackGui template ContainerType CListModelBase::sortContainerByColumn(const ContainerType &container, int column, Qt::SortOrder order) const { - if (container.size() < 2 || !this->m_columns.isSortable(column)) { return container; } // nothing to do + if (m_modelDestroyed) { return container; } + if (container.size() < 2 || !this->m_columns.isSortable(column)) + { + return container; // nothing to do + } // this is the only part not really thread safe, but columns do not change so far BlackMisc::CPropertyIndex propertyIndex = this->m_columns.columnToSortPropertyIndex(column); Q_ASSERT(!propertyIndex.isEmpty()); - if (propertyIndex.isEmpty()) { return container; } // at release build do nothing + if (propertyIndex.isEmpty()) + { + return container; // at release build do nothing + } // sort the values const auto p = [ = ](const ObjectType & a, const ObjectType & b) -> bool @@ -447,20 +538,48 @@ namespace BlackGui return sorted; } + template + QMimeData *CListModelBase::mimeData(const QModelIndexList &indexes) const + { + QMimeData *mimeData = new QMimeData(); + if (indexes.isEmpty()) { return mimeData; } + + ContainerType container; + QList rows; // avoid redundant objects + + // Indexes are per row and column + for (const QModelIndex &index : indexes) + { + if (!index.isValid()) { continue; } + int r = index.row(); + if (rows.contains(r)) { continue; } + container.push_back(this->at(index)); + rows.append(r); + } + + // to JSON via CVariant + const QJsonDocument containerJson(CVariant::fromValue(container).toJson()); + const QString jsonString(containerJson.toJson(QJsonDocument::Compact)); + + mimeData->setData(CGuiUtility::swiftJsonDragAndDropMimeType(), jsonString.toUtf8()); + return mimeData; + } + // see here for the reason of thess forward instantiations // http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html template class CListModelBase; template class CListModelBase; template class CListModelBase; + template class CListModelBase; template class CListModelBase; - template class CListModelBase; template class CListModelBase; template class CListModelBase; + template class CListModelBase; + template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; - template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; diff --git a/src/blackgui/models/listmodelbase.h b/src/blackgui/models/listmodelbase.h index 9c77108c8..bef1010cd 100644 --- a/src/blackgui/models/listmodelbase.h +++ b/src/blackgui/models/listmodelbase.h @@ -14,7 +14,7 @@ #include "blackgui/blackguiexport.h" #include "blackgui/models/columns.h" -#include "blackgui/models/listmodelfilter.h" +#include "blackgui/models/modelfilter.h" #include "blackmisc/worker.h" #include "blackmisc/propertyindex.h" #include @@ -75,12 +75,27 @@ namespace BlackGui //! Get sort order virtual Qt::SortOrder getSortOrder() const { return this->m_sortOrder; } - //! \copydoc QAbstractTableModel::flags - Qt::ItemFlags flags(const QModelIndex &index) const override; - //! Translation context virtual const QString &getTranslationContext() const; + //! \copydoc QAbstractItemModel::flags + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + + //! \copydoc QAbstractItemModel::supportedDragActions + virtual Qt::DropActions supportedDragActions() const override; + + //! \copydoc QAbstractItemModel::supportedDropActions + virtual Qt::DropActions supportedDropActions() const override; + + //! \copydoc QAbstractItemModel::supportedDropActions + virtual QStringList mimeTypes() const override; + + //! Mark as about to be destroyed, normally marked from view + void markDestroyed(); + + //! Model about to be destroyed? + bool isModelDestroyed(); + signals: //! Asynchronous update finished void asyncUpdateFinished(); @@ -101,19 +116,15 @@ namespace BlackGui //! Constructor //! \param translationContext I18N context //! \param parent - CListModelBaseNonTemplate(const QString &translationContext, QObject *parent = nullptr) - : QAbstractItemModel(parent), m_columns(translationContext), m_sortedColumn(-1), m_sortOrder(Qt::AscendingOrder) - { - // non unique default name, set translation context as default - this->setObjectName(translationContext); - } + CListModelBaseNonTemplate(const QString &translationContext, QObject *parent = nullptr); //! Helper method with template free signature virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort) = 0; - CColumns m_columns; //!< columns metadata - int m_sortedColumn; //!< current sort column - Qt::SortOrder m_sortOrder; //!< sort order (asc/desc) + CColumns m_columns; //!< columns metadata + int m_sortedColumn; //!< current sort column + Qt::SortOrder m_sortOrder; //!< sort order (asc/desc) + bool m_modelDestroyed = false; //!< model is about to be destroyed }; //! List model @@ -201,6 +212,9 @@ namespace BlackGui //! Empty? virtual bool isEmpty() const; + //! \copydoc QAbstractItemModel::mimeData + virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; + //! Filter available bool hasFilter() const; @@ -208,7 +222,7 @@ namespace BlackGui void removeFilter(); //! Set the filter - void setFilter(std::unique_ptr > &filter); + void takeFilterOwnership(std::unique_ptr > &filter); protected: std::unique_ptr > m_filter; //!< Used filter @@ -233,7 +247,6 @@ namespace BlackGui void emitRowCountChanged(); ContainerType m_container; //!< used container ContainerType m_containerFiltered; //!< cache for filtered container data - }; } // namespace