refs #452, model base class

* drag and drop support
* model can be marked as destoyed when view is about to be destroyed (to cancel pending actions)
This commit is contained in:
Klaus Basan
2015-09-23 20:05:41 +02:00
committed by Mathew Sutcliffe
parent 0d27351e11
commit fb4baa0ef8
2 changed files with 171 additions and 39 deletions

View File

@@ -7,10 +7,17 @@
* contained in the LICENSE file. * 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 "listmodelbase.h"
#include "allmodelcontainers.h" #include "allmodelcontainers.h"
#include "blackgui/guiutility.h"
#include "blackmisc/variant.h" #include "blackmisc/variant.h"
#include "blackmisc/json.h"
#include "blackmisc/blackmiscfreefunctions.h" #include "blackmisc/blackmiscfreefunctions.h"
#include <QMimeData>
#include <QJsonDocument>
using namespace BlackMisc; using namespace BlackMisc;
@@ -27,10 +34,19 @@ namespace BlackGui
QVariant CListModelBaseNonTemplate::headerData(int section, Qt::Orientation orientation, int role) const 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); bool handled = (role == Qt::DisplayRole || role == Qt::ToolTipRole || role == Qt::InitialSortOrderRole);
if (!handled) {return QVariant();} if (!handled)
if (section < 0 || section >= this->m_columns.size()) { return QVariant(); } {
return QVariant();
}
if (section < 0 || section >= this->m_columns.size())
{
return QVariant();
}
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
{ {
@@ -90,13 +106,14 @@ namespace BlackGui
if (!index.isValid()) { return f; } if (!index.isValid()) { return f; }
bool editable = this->m_columns.isEditable(index); bool editable = this->m_columns.isEditable(index);
f = editable ? (f | Qt::ItemIsEditable) : (f ^ Qt::ItemIsEditable); 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; return f;
} }
@@ -105,11 +122,44 @@ namespace BlackGui
return m_columns.getTranslationContext(); 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) int CListModelBaseNonTemplate::ps_updateContainer(const CVariant &variant, bool sort)
{ {
return this->performUpdateContainer(variant, 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 <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
int CListModelBase<ObjectType, ContainerType>::rowCount(const QModelIndex &parentIndex) const int CListModelBase<ObjectType, ContainerType>::rowCount(const QModelIndex &parentIndex) const
{ {
@@ -144,14 +194,26 @@ namespace BlackGui
bool CListModelBase<ObjectType, ContainerType>::setData(const QModelIndex &index, const QVariant &value, int role) bool CListModelBase<ObjectType, ContainerType>::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
Qt::ItemDataRole dataRole = static_cast<Qt::ItemDataRole>(role); Qt::ItemDataRole dataRole = static_cast<Qt::ItemDataRole>(role);
if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole)) { return false; } if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole))
{
return false;
}
// check / init // check / init
if (!this->isValidIndex(index)) { return false; } if (!this->isValidIndex(index))
if (!this->m_columns.isEditable(index)) { return false; } {
return false;
}
if (!this->m_columns.isEditable(index))
{
return false;
}
const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); const CDefaultFormatter *formatter = this->m_columns.getFormatter(index);
Q_ASSERT(formatter); Q_ASSERT(formatter);
if (!formatter) { return false; } if (!formatter)
{
return false;
}
ObjectType obj = this->m_container[index.row()]; ObjectType obj = this->m_container[index.row()];
ObjectType currentObject(obj); ObjectType currentObject(obj);
@@ -175,6 +237,8 @@ namespace BlackGui
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
int CListModelBase<ObjectType, ContainerType>::update(const ContainerType &container, bool sort) int CListModelBase<ObjectType, ContainerType>::update(const ContainerType &container, bool sort)
{ {
if (m_modelDestroyed) { return 0; }
// Keep sorting out of begin/end reset model // Keep sorting out of begin/end reset model
ContainerType sortedContainer; ContainerType sortedContainer;
int oldSize = this->m_container.size(); int oldSize = this->m_container.size();
@@ -191,13 +255,17 @@ namespace BlackGui
this->endResetModel(); this->endResetModel();
int newSize = this->m_container.size(); int newSize = this->m_container.size();
if (oldSize != newSize) { this->emitRowCountChanged(); } if (oldSize != newSize)
{
this->emitRowCountChanged();
}
return newSize; return newSize;
} }
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
void CListModelBase<ObjectType, ContainerType>::update(const QModelIndex &index, const ObjectType &object) void CListModelBase<ObjectType, ContainerType>::update(const QModelIndex &index, const ObjectType &object)
{ {
if (m_modelDestroyed) { return; }
if (index.row() >= this->m_container.size()) { return; } if (index.row() >= this->m_container.size()) { return; }
this->m_container[index.row()] = object; this->m_container[index.row()] = object;
@@ -216,6 +284,7 @@ namespace BlackGui
CWorker *CListModelBase<ObjectType, ContainerType>::updateAsync(const ContainerType &container, bool sort) CWorker *CListModelBase<ObjectType, ContainerType>::updateAsync(const ContainerType &container, bool sort)
{ {
Q_UNUSED(sort); Q_UNUSED(sort);
if (m_modelDestroyed) { return nullptr; }
auto sortColumn = this->getSortColumn(); auto sortColumn = this->getSortColumn();
auto sortOrder = this->getSortOrder(); auto sortOrder = this->getSortOrder();
CWorker *worker = BlackMisc::CWorker::fromTask(this, "ModelSort", [this, container, sortColumn, sortOrder]() CWorker *worker = BlackMisc::CWorker::fromTask(this, "ModelSort", [this, container, sortColumn, sortOrder]()
@@ -224,6 +293,7 @@ namespace BlackGui
}); });
worker->thenWithResult<ContainerType>(this, [this](const ContainerType &sortedContainer) worker->thenWithResult<ContainerType>(this, [this](const ContainerType &sortedContainer)
{ {
if (this->m_modelDestroyed) { return; }
this->ps_updateContainer(CVariant::from(sortedContainer), false); this->ps_updateContainer(CVariant::from(sortedContainer), false);
}); });
worker->then(this, &CListModelBase::asyncUpdateFinished); worker->then(this, &CListModelBase::asyncUpdateFinished);
@@ -233,6 +303,7 @@ namespace BlackGui
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
void CListModelBase<ObjectType, ContainerType>::updateContainerMaybeAsync(const ContainerType &container, bool sort) void CListModelBase<ObjectType, ContainerType>::updateContainerMaybeAsync(const ContainerType &container, bool sort)
{ {
if (m_modelDestroyed) { return; }
if (container.size() > asyncThreshold && sort) if (container.size() > asyncThreshold && sort)
{ {
// larger container with sorting // larger container with sorting
@@ -247,7 +318,7 @@ namespace BlackGui
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
bool CListModelBase<ObjectType, ContainerType>::hasFilter() const bool CListModelBase<ObjectType, ContainerType>::hasFilter() const
{ {
return m_filter ? true : false; return m_filter && m_filter->isValid() ? true : false;
} }
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
@@ -262,9 +333,13 @@ namespace BlackGui
} }
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
void CListModelBase<ObjectType, ContainerType>::setFilter(std::unique_ptr<IModelFilter<ContainerType> > &filter) void CListModelBase<ObjectType, ContainerType>::takeFilterOwnership(std::unique_ptr<IModelFilter<ContainerType> > &filter)
{ {
if (!filter) { this->removeFilter(); return; } // empty filter if (!filter)
{
this->removeFilter(); // empty filter
return;
}
if (filter->isValid()) if (filter->isValid())
{ {
this->m_filter = std::move(filter); this->m_filter = std::move(filter);
@@ -371,7 +446,10 @@ namespace BlackGui
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
const ContainerType &CListModelBase<ObjectType, ContainerType>::getContainerOrFilteredContainer() const const ContainerType &CListModelBase<ObjectType, ContainerType>::getContainerOrFilteredContainer() const
{ {
if (!this->hasFilter()) { return this->m_container; } if (!this->hasFilter())
{
return this->m_container;
}
return m_containerFiltered; return m_containerFiltered;
} }
@@ -409,7 +487,10 @@ namespace BlackGui
// new order // new order
this->m_sortedColumn = column; this->m_sortedColumn = column;
this->m_sortOrder = order; 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 // sort the values
this->updateContainerMaybeAsync(this->m_container, true); this->updateContainerMaybeAsync(this->m_container, true);
@@ -419,7 +500,10 @@ namespace BlackGui
void CListModelBase<ObjectType, ContainerType>::truncate(int maxNumber, bool forceSort) void CListModelBase<ObjectType, ContainerType>::truncate(int maxNumber, bool forceSort)
{ {
if (this->rowCount() <= maxNumber) { return; } 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()); ContainerType container(this->getContainer());
container.truncate(maxNumber); container.truncate(maxNumber);
this->updateContainerMaybeAsync(container, false); this->updateContainerMaybeAsync(container, false);
@@ -428,12 +512,19 @@ namespace BlackGui
template <typename ObjectType, typename ContainerType> template <typename ObjectType, typename ContainerType>
ContainerType CListModelBase<ObjectType, ContainerType>::sortContainerByColumn(const ContainerType &container, int column, Qt::SortOrder order) const ContainerType CListModelBase<ObjectType, ContainerType>::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 // this is the only part not really thread safe, but columns do not change so far
BlackMisc::CPropertyIndex propertyIndex = this->m_columns.columnToSortPropertyIndex(column); BlackMisc::CPropertyIndex propertyIndex = this->m_columns.columnToSortPropertyIndex(column);
Q_ASSERT(!propertyIndex.isEmpty()); 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 // sort the values
const auto p = [ = ](const ObjectType & a, const ObjectType & b) -> bool const auto p = [ = ](const ObjectType & a, const ObjectType & b) -> bool
@@ -447,20 +538,48 @@ namespace BlackGui
return sorted; return sorted;
} }
template <typename ObjectType, typename ContainerType>
QMimeData *CListModelBase<ObjectType, ContainerType>::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
if (indexes.isEmpty()) { return mimeData; }
ContainerType container;
QList<int> 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 // see here for the reason of thess forward instantiations
// http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html // http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html
template class CListModelBase<BlackMisc::CIdentifier, BlackMisc::CIdentifierList>; template class CListModelBase<BlackMisc::CIdentifier, BlackMisc::CIdentifierList>;
template class CListModelBase<BlackMisc::CStatusMessage, BlackMisc::CStatusMessageList>; template class CListModelBase<BlackMisc::CStatusMessage, BlackMisc::CStatusMessageList>;
template class CListModelBase<BlackMisc::CNameVariantPair, BlackMisc::CNameVariantPairList>; template class CListModelBase<BlackMisc::CNameVariantPair, BlackMisc::CNameVariantPairList>;
template class CListModelBase<BlackMisc::CCountry, BlackMisc::CCountryList>;
template class CListModelBase<BlackMisc::Aviation::CAtcStation, BlackMisc::Aviation::CAtcStationList>; template class CListModelBase<BlackMisc::Aviation::CAtcStation, BlackMisc::Aviation::CAtcStationList>;
template class CListModelBase<BlackMisc::Aviation::CAircraft, BlackMisc::Aviation::CAircraftList>;
template class CListModelBase<BlackMisc::Aviation::CAirport, BlackMisc::Aviation::CAirportList>; template class CListModelBase<BlackMisc::Aviation::CAirport, BlackMisc::Aviation::CAirportList>;
template class CListModelBase<BlackMisc::Aviation::CLivery, BlackMisc::Aviation::CLiveryList>; template class CListModelBase<BlackMisc::Aviation::CLivery, BlackMisc::Aviation::CLiveryList>;
template class CListModelBase<BlackMisc::Aviation::CAircraftIcaoCode, BlackMisc::Aviation::CAircraftIcaoCodeList>;
template class CListModelBase<BlackMisc::Aviation::CAirlineIcaoCode, BlackMisc::Aviation::CAirlineIcaoCodeList>;
template class CListModelBase<BlackMisc::Network::CServer, BlackMisc::Network::CServerList>; template class CListModelBase<BlackMisc::Network::CServer, BlackMisc::Network::CServerList>;
template class CListModelBase<BlackMisc::Network::CUser, BlackMisc::Network::CUserList>; template class CListModelBase<BlackMisc::Network::CUser, BlackMisc::Network::CUserList>;
template class CListModelBase<BlackMisc::Network::CTextMessage, BlackMisc::Network::CTextMessageList>; template class CListModelBase<BlackMisc::Network::CTextMessage, BlackMisc::Network::CTextMessageList>;
template class CListModelBase<BlackMisc::Network::CClient, BlackMisc::Network::CClientList>; template class CListModelBase<BlackMisc::Network::CClient, BlackMisc::Network::CClientList>;
template class CListModelBase<BlackMisc::Network::CAircraftMapping, BlackMisc::Network::CAircraftMappingList>;
template class CListModelBase<BlackMisc::Simulation::CAircraftModel, BlackMisc::Simulation::CAircraftModelList>; template class CListModelBase<BlackMisc::Simulation::CAircraftModel, BlackMisc::Simulation::CAircraftModelList>;
template class CListModelBase<BlackMisc::Simulation::CSimulatedAircraft, BlackMisc::Simulation::CSimulatedAircraftList>; template class CListModelBase<BlackMisc::Simulation::CSimulatedAircraft, BlackMisc::Simulation::CSimulatedAircraftList>;
template class CListModelBase<BlackMisc::Simulation::CDistributor, BlackMisc::Simulation::CDistributorList>; template class CListModelBase<BlackMisc::Simulation::CDistributor, BlackMisc::Simulation::CDistributorList>;

View File

@@ -14,7 +14,7 @@
#include "blackgui/blackguiexport.h" #include "blackgui/blackguiexport.h"
#include "blackgui/models/columns.h" #include "blackgui/models/columns.h"
#include "blackgui/models/listmodelfilter.h" #include "blackgui/models/modelfilter.h"
#include "blackmisc/worker.h" #include "blackmisc/worker.h"
#include "blackmisc/propertyindex.h" #include "blackmisc/propertyindex.h"
#include <QAbstractItemModel> #include <QAbstractItemModel>
@@ -75,12 +75,27 @@ namespace BlackGui
//! Get sort order //! Get sort order
virtual Qt::SortOrder getSortOrder() const { return this->m_sortOrder; } virtual Qt::SortOrder getSortOrder() const { return this->m_sortOrder; }
//! \copydoc QAbstractTableModel::flags
Qt::ItemFlags flags(const QModelIndex &index) const override;
//! Translation context //! Translation context
virtual const QString &getTranslationContext() const; 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: signals:
//! Asynchronous update finished //! Asynchronous update finished
void asyncUpdateFinished(); void asyncUpdateFinished();
@@ -101,19 +116,15 @@ namespace BlackGui
//! Constructor //! Constructor
//! \param translationContext I18N context //! \param translationContext I18N context
//! \param parent //! \param parent
CListModelBaseNonTemplate(const QString &translationContext, QObject *parent = nullptr) 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);
}
//! Helper method with template free signature //! Helper method with template free signature
virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort) = 0; virtual int performUpdateContainer(const BlackMisc::CVariant &variant, bool sort) = 0;
CColumns m_columns; //!< columns metadata CColumns m_columns; //!< columns metadata
int m_sortedColumn; //!< current sort column int m_sortedColumn; //!< current sort column
Qt::SortOrder m_sortOrder; //!< sort order (asc/desc) Qt::SortOrder m_sortOrder; //!< sort order (asc/desc)
bool m_modelDestroyed = false; //!< model is about to be destroyed
}; };
//! List model //! List model
@@ -201,6 +212,9 @@ namespace BlackGui
//! Empty? //! Empty?
virtual bool isEmpty() const; virtual bool isEmpty() const;
//! \copydoc QAbstractItemModel::mimeData
virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
//! Filter available //! Filter available
bool hasFilter() const; bool hasFilter() const;
@@ -208,7 +222,7 @@ namespace BlackGui
void removeFilter(); void removeFilter();
//! Set the filter //! Set the filter
void setFilter(std::unique_ptr<IModelFilter<ContainerType> > &filter); void takeFilterOwnership(std::unique_ptr<IModelFilter<ContainerType> > &filter);
protected: protected:
std::unique_ptr<IModelFilter<ContainerType> > m_filter; //!< Used filter std::unique_ptr<IModelFilter<ContainerType> > m_filter; //!< Used filter
@@ -233,7 +247,6 @@ namespace BlackGui
void emitRowCountChanged(); void emitRowCountChanged();
ContainerType m_container; //!< used container ContainerType m_container; //!< used container
ContainerType m_containerFiltered; //!< cache for filtered container data ContainerType m_containerFiltered; //!< cache for filtered container data
}; };
} // namespace } // namespace