diff options
-rw-r--r-- | qt-models/filtermodels.cpp | 154 | ||||
-rw-r--r-- | qt-models/filtermodels.h | 10 |
2 files changed, 109 insertions, 55 deletions
diff --git a/qt-models/filtermodels.cpp b/qt-models/filtermodels.cpp index beeb7501b..b71a660ae 100644 --- a/qt-models/filtermodels.cpp +++ b/qt-models/filtermodels.cpp @@ -2,6 +2,7 @@ #include "qt-models/filtermodels.h" #include "qt-models/models.h" #include "core/display.h" +#include "core/qthelper.h" #include "core/subsurface-string.h" #include "qt-models/divetripmodel.h" @@ -26,16 +27,85 @@ CREATE_INSTANCE_METHOD(LocationFilterModel) CREATE_INSTANCE_METHOD(SuitsFilterModel) CREATE_INSTANCE_METHOD(MultiFilterSortModel) -FilterModelBase::FilterModelBase(QObject *parent) : QStringListModel(parent), +FilterModelBase::FilterModelBase(QObject *parent) : QAbstractListModel(parent), anyChecked(false), negate(false) { } -// Update the stringList and the items array. +// Get index of item with given name, but ignore last item, as this +// is the "Show Empty Tags" item. Return -1 for not found. +int FilterModelBase::indexOf(const QString &name) const +{ + for (int i = 0; i < rowCount() - 1; i++) { + if (name == items[i].name) + return i; + } + return -1; +} + +int FilterModelBase::findInsertionIndex(const QString &name) +{ + // Find insertion position. Note: we search only up to the last + // item, because the last item is the "Show Empty Tags" item. + // N.B: We might do a binary search using std::lower_bound() + int i; + for (i = 0; i < rowCount() - 1; i++) { + if (name < items[i].name) + return i; + } + return i; +} + +void FilterModelBase::addItem(const QString &name, bool checked, int count) +{ + int idx = findInsertionIndex(name); + beginInsertRows(QModelIndex(), idx, idx); + items.insert(items.begin() + idx, { name, checked, count }); + endInsertRows(); +} + +void FilterModelBase::changeName(const QString &oldName, const QString &newName) +{ + if (oldName.isEmpty() || newName.isEmpty() || oldName == newName) + return; + int oldIndex = indexOf(oldName); + if (oldIndex < 0) + return; + int newIndex = indexOf(newName); + + if (newIndex >= 0) { + // If there was already an entry with the new name, we are merging entries. + // Thus, if the old entry was selected, also select the new entry. + if (items[oldIndex].checked && !items[newIndex].checked) { + items[newIndex].checked = true; + dataChanged(createIndex(newIndex, 0), createIndex(newIndex, 0)); + } + // Now, delete the old item + beginRemoveRows(QModelIndex(), oldIndex, oldIndex); + items.erase(items.begin() + oldIndex); + endRemoveRows(); + } else { + // There was no entry of the same name. We might have to move the item. + newIndex = findInsertionIndex(newName); + if (oldIndex != newIndex && oldIndex + 1 != newIndex) { + beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex); + moveInVector(items, oldIndex, oldIndex + 1, newIndex); + endMoveRows(); + } + + // The item was moved, but the name still has to be modified + items[newIndex].name = newName; + dataChanged(createIndex(newIndex, 0), createIndex(newIndex, 0)); + } +} + +// Update the the items array. // The last item is supposed to be the "Show Empty Tags" entry. void FilterModelBase::updateList(const QStringList &newList) { + beginResetModel(); + // Keep copy of the old items array to reimport the checked state later. // Note that by using std::move(), this is an essentially free operation: // The data is moved from the old array to the new one and the old array @@ -46,16 +116,18 @@ void FilterModelBase::updateList(const QStringList &newList) // flag in an undefined state (since we didn't define a default constructor). items.resize(newList.count()); - // First, reset all checked states to false + // First, reset all checked states to false and set the names anyChecked = false; - for (Item &item: items) - item.checked = false; + for (int i = 0; i < rowCount(); ++i) { + items[i].name = newList[i]; + items[i].checked = false; + } // Then, restore the checked state. Ignore the last item, since // this is the "Show Empty Tags" entry. for (int i = 0; i < (int)oldItems.size() - 1; i++) { if (oldItems[i].checked) { - int ind = newList.indexOf(stringList()[i]); + int ind = newList.indexOf(oldItems[i].name); if (ind >= 0 && ind < newList.count() - 1) { items[ind].checked = true; anyChecked = true; @@ -79,12 +151,12 @@ void FilterModelBase::updateList(const QStringList &newList) if (!items.empty()) items.back().count = countDives(""); - setStringList(newList); + endResetModel(); } Qt::ItemFlags FilterModelBase::flags(const QModelIndex &index) const { - return QStringListModel::flags(index) | Qt::ItemIsUserCheckable; + return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable; } bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, int role) @@ -104,13 +176,18 @@ bool FilterModelBase::setData(const QModelIndex &index, const QVariant &value, i return false; } +int FilterModelBase::rowCount(const QModelIndex &) const +{ + return items.size(); +} + QVariant FilterModelBase::data(const QModelIndex &index, int role) const { if (role == Qt::CheckStateRole) { return items[index.row()].checked ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::DisplayRole) { - int row = index.row(); - return QStringLiteral("%1 (%2)").arg(stringList()[row], QString::number(items[row].count)); + const Item &item = items[index.row()]; + return QStringLiteral("%1 (%2)").arg(item.name, QString::number(item.count)); } return QVariant(); } @@ -168,10 +245,9 @@ bool SuitsFilterModel::doFilter(const dive *d) const return items[rowCount() - 1].checked != negate; // there is a suit selected - QStringList suitList = stringList(); // Ignore last item, since this is the "Show Empty Tags" entry for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && suit == suitList[i]) + if (items[i].checked && suit == items[i].name) return !negate; } return negate; @@ -233,16 +309,12 @@ bool TagFilterModel::doFilter(const dive *d) const return items[rowCount() - 1].checked != negate; // have at least one tag. - QStringList tagList = stringList(); - if (!tagList.isEmpty()) { - tagList.removeLast(); // remove the "Show Empty Tags"; - while (head) { - QString tagName(head->tag->name); - int index = tagList.indexOf(tagName); - if (index >= 0 && items[index].checked) - return !negate; - head = head->next; - } + while (head) { + QString tagName(head->tag->name); + int index = indexOf(tagName); + if (index >= 0 && items[index].checked) + return !negate; + head = head->next; } return negate; } @@ -274,10 +346,9 @@ bool BuddyFilterModel::doFilter(const dive *d) const return items[rowCount() - 1].checked != negate; // have at least one buddy - QStringList buddyList = stringList(); // Ignore last item, since this is the "Show Empty Tags" entry for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && personsList.contains(buddyList[i], Qt::CaseInsensitive)) + if (items[i].checked && personsList.contains(items[i].name, Qt::CaseInsensitive)) return !negate; } return negate; @@ -325,10 +396,9 @@ bool LocationFilterModel::doFilter(const dive *d) const return items[rowCount() - 1].checked != negate; // There is a location selected - QStringList locationList = stringList(); // Ignore last item, since this is the "Show Empty Tags" entry for (int i = 0; i < rowCount() - 1; i++) { - if (items[i].checked && location == locationList[i]) + if (items[i].checked && location == items[i].name) return !negate; } return negate; @@ -350,36 +420,14 @@ void LocationFilterModel::repopulate() updateList(list); } -void LocationFilterModel::changeName(const QString &oldName, const QString &newName) -{ - if (oldName.isEmpty() || newName.isEmpty() || oldName == newName) - return; - QStringList list = stringList(); - int oldIndex = list.indexOf(oldName); - if (oldIndex < 0) - return; - int newIndex = list.indexOf(newName); - list[oldIndex] = newName; - - // If there was already an entry with the new name, we are merging entries. - // Thus, if the old entry was selected, also select the new entry. - if (newIndex >= 0 && items[oldIndex].checked) - items[newIndex].checked = true; - setStringList(list); -} - void LocationFilterModel::addName(const QString &newName) { - // If any item is checked and a new location is added, add the name - // of the new location in front of the list and mark it as checked. - // Thus, on subsequent repopulation of the list, the new entry will - // be registered as already checked. - QStringList list = stringList(); - if (!anyChecked || newName.isEmpty() || list.indexOf(newName) >= 0) + if (newName.isEmpty() || indexOf(newName) >= 0) return; - list.prepend(newName); - items.insert(items.begin(), { true }); - setStringList(list); + int count = countDives(qPrintable(newName)); + // If any old item was checked, also check the new one so that + // dives with the added dive site are shown. + addItem(newName, anyChecked, count); } MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyModel(parent), diff --git a/qt-models/filtermodels.h b/qt-models/filtermodels.h index 846949809..7e5aae727 100644 --- a/qt-models/filtermodels.h +++ b/qt-models/filtermodels.h @@ -9,14 +9,20 @@ struct dive; -class FilterModelBase : public QStringListModel { +class FilterModelBase : public QAbstractListModel { Q_OBJECT +private: + int findInsertionIndex(const QString &name); protected: struct Item { + QString name; bool checked; int count; }; std::vector<Item> items; + int indexOf(const QString &name) const; + void addItem(const QString &name, bool checked, int count); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; public: virtual bool doFilter(const dive *d) const = 0; void clearFilter(); @@ -27,6 +33,7 @@ public: public slots: void setNegate(bool negate); + void changeName(const QString &oldName, const QString &newName); protected: explicit FilterModelBase(QObject *parent = 0); void updateList(const QStringList &new_list); @@ -73,7 +80,6 @@ public: public slots: void repopulate(); - void changeName(const QString &oldName, const QString &newName); void addName(const QString &newName); private: |