File indexing completed on 2024-05-12 16:01:27

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef __KIS_CATEGORIZED_LIST_MODEL_H
0008 #define __KIS_CATEGORIZED_LIST_MODEL_H
0009 
0010 #include <QAbstractListModel>
0011 #include <QSortFilterProxyModel>
0012 #include "kis_categories_mapper.h"
0013 
0014 class KRITAUI_EXPORT __CategorizedListModelBase : public QAbstractListModel
0015 {
0016     Q_OBJECT
0017 
0018 public:
0019     enum AdditionalRoles {
0020         IsHeaderRole       = Qt::UserRole + 1,
0021         ExpandCategoryRole = Qt::UserRole + 2,
0022         SortRole           = Qt::UserRole + 3,
0023         isLockedRole       = Qt::UserRole + 4,
0024         isLockableRole     = Qt::UserRole + 5,
0025         isToggledRole      = Qt::UserRole + 6
0026     };
0027 
0028 public:
0029     __CategorizedListModelBase(QObject *parent);
0030     ~__CategorizedListModelBase() override;
0031 
0032 private Q_SLOTS:
0033 
0034     void slotRowChanged(int row) {
0035         QModelIndex changedIndex(index(row));
0036         emit dataChanged(changedIndex, changedIndex);
0037     }
0038 
0039     void slotBeginInsertRow(int row) {
0040         beginInsertRows(QModelIndex(), row, row);
0041     }
0042 
0043     void slotEndInsertRow() {
0044         endInsertRows();
0045     }
0046 
0047     void slotBeginRemoveRow(int row) {
0048         beginRemoveRows(QModelIndex(), row, row);
0049     }
0050 
0051     void slotEndRemoveRow() {
0052         endRemoveRows();
0053     }
0054 };
0055 
0056 template<class TEntry, class TEntryToQStringConverter>
0057 class KisCategorizedListModel : public __CategorizedListModelBase
0058 {
0059 public:
0060     typedef TEntry Entry_Type;
0061     typedef KisCategoriesMapper<TEntry, TEntryToQStringConverter> SpecificCategoriesMapper;
0062     typedef typename SpecificCategoriesMapper::DataItem DataItem;
0063 
0064 public:
0065     KisCategorizedListModel(QObject *parent = 0)
0066         : __CategorizedListModelBase(parent)
0067     {
0068         connect(&m_mapper, SIGNAL(rowChanged(int)), SLOT(slotRowChanged(int))); // helps with category expand menu
0069         connect(&m_mapper, SIGNAL(beginInsertRow(int)), SLOT(slotBeginInsertRow(int)));
0070         connect(&m_mapper, SIGNAL(endInsertRow()), SLOT(slotEndInsertRow()));
0071         connect(&m_mapper, SIGNAL(beginRemoveRow(int)), SLOT(slotBeginRemoveRow(int)));
0072         connect(&m_mapper, SIGNAL(endRemoveRow()), SLOT(slotEndRemoveRow()));
0073 
0074 
0075 
0076     }
0077 
0078     int rowCount(const QModelIndex& parent) const override {
0079         Q_UNUSED(parent);
0080         return m_mapper.rowCount();
0081     }
0082 
0083     QVariant data(const QModelIndex& idx, int role = Qt::DisplayRole) const override {
0084         if (!idx.isValid()) return QVariant();
0085 
0086         typename SpecificCategoriesMapper::DataItem *item =
0087             m_mapper.itemFromRow(idx.row());
0088         Q_ASSERT(item);
0089 
0090         switch (role) {
0091         case IsHeaderRole:
0092             return item->isCategory();
0093         case ExpandCategoryRole:
0094             return item->isCategory() ? item->isExpanded() : item->parentCategory()->isExpanded();
0095         case Qt::ToolTipRole:
0096         case Qt::DisplayRole:
0097             return item->name();
0098         case Qt::CheckStateRole:
0099             return item->isCheckable() ? item->isChecked() ? Qt::Checked : Qt::Unchecked : QVariant();
0100         case SortRole:
0101             return item->isCategory() ? item->name() : item->parentCategory()->name() + item->name();
0102         case isLockedRole:
0103             return item->isLocked();
0104         case isLockableRole:
0105             return item->isLockable();
0106         case isToggledRole:
0107             return item->isToggled();
0108 
0109         }
0110 
0111         return QVariant();
0112     }
0113 
0114     bool setData(const QModelIndex& idx, const QVariant& value, int role = Qt::EditRole) override {
0115         if (!idx.isValid()) return false;
0116 
0117         typename SpecificCategoriesMapper::DataItem *item =
0118             m_mapper.itemFromRow(idx.row());
0119         Q_ASSERT(item);
0120 
0121         switch (role) {
0122         case ExpandCategoryRole:
0123             Q_ASSERT(item->isCategory());
0124             item->setExpanded(value.toBool());
0125             break;
0126         case Qt::CheckStateRole:
0127             Q_ASSERT(item->isCheckable());
0128             item->setChecked(value.toInt() == Qt::Checked);            
0129             break;        
0130         }
0131 
0132         // dataChanged() needs a QVector even though we are just passing one
0133         QVector<int> roles;
0134         roles.append(role);
0135 
0136         emit dataChanged(idx, idx, roles);
0137         return true;
0138     }
0139 
0140     Qt::ItemFlags flags(const QModelIndex& idx) const override {
0141         if (!idx.isValid()) return Qt::NoItemFlags;
0142 
0143         typename SpecificCategoriesMapper::DataItem *item =
0144             m_mapper.itemFromRow(idx.row());
0145         Q_ASSERT(item);
0146 
0147         Qt::ItemFlags flags = Qt::NoItemFlags;
0148 
0149         if (item->isEnabled()) {
0150             flags |= Qt::ItemIsEnabled;
0151         }
0152 
0153         if (!item->isCategory()) {
0154             flags |= Qt::ItemIsSelectable;
0155 
0156             if (item->isCheckable()) {
0157                 flags |= Qt::ItemIsUserCheckable;
0158             }
0159         }
0160 
0161         return flags;
0162     }
0163 
0164     QModelIndex indexOf(const TEntry& entry) const {
0165         typename SpecificCategoriesMapper::DataItem *item =
0166             m_mapper.fetchOneEntry(entry);
0167 
0168         return index(m_mapper.rowFromItem(item));
0169     }
0170 
0171     bool entryAt(TEntry& entry, QModelIndex index) const {
0172         int row = index.row();
0173         if (row < 0 || row >= m_mapper.rowCount()) return false;
0174 
0175         typename SpecificCategoriesMapper::DataItem *item =
0176             m_mapper.itemFromRow(row);
0177 
0178         if (!item->isCategory()) {
0179             entry = *item->data();
0180             return true;
0181         }
0182 
0183         return false;
0184     }
0185 
0186     SpecificCategoriesMapper* categoriesMapper() {
0187         return &m_mapper;
0188     }
0189 
0190     const SpecificCategoriesMapper* categoriesMapper() const {
0191         return &m_mapper;
0192     }
0193 
0194 private:
0195     SpecificCategoriesMapper m_mapper;
0196 };
0197 
0198 template<class TModel>
0199 class KRITAUI_EXPORT KisSortedCategorizedListModel : public QSortFilterProxyModel
0200 {
0201     typedef typename TModel::Entry_Type Entry_Type;
0202 
0203 public:
0204 
0205     KisSortedCategorizedListModel(QObject *parent)
0206         : QSortFilterProxyModel(parent)
0207     {
0208     }
0209 
0210     QModelIndex indexOf(const Entry_Type& entry) const {
0211         /**
0212          * We don't use the source model's indexOf(), because
0213          * the items might be duplicated and we need to return the
0214          * topmost one in the sorted order.
0215          */
0216 
0217         Entry_Type e;
0218 
0219         for (int i = 0; i < rowCount(); i++) {
0220             QModelIndex index = this->index(i, 0);
0221 
0222             if (entryAt(e, index) && e == entry) {
0223                 return index;
0224             }
0225         }
0226 
0227         return QModelIndex();
0228     }
0229 
0230     bool entryAt(Entry_Type &entry, QModelIndex index) const {
0231         QModelIndex srcIndex = mapToSource(index);
0232         return m_model->entryAt(entry, srcIndex);
0233     }
0234 
0235 protected:
0236     void initializeModel(TModel *model) {
0237         m_model = model;
0238         setSourceModel(model);
0239         setSortRole(TModel::SortRole);
0240     }
0241 
0242     bool lessThanPriority(const QModelIndex &left,
0243                           const QModelIndex &right,
0244                           const QString &priorityCategory) const {
0245 
0246         QString leftKey = sourceModel()->data(left, sortRole()).toString();
0247         QString rightKey = sourceModel()->data(right, sortRole()).toString();
0248 
0249         bool leftIsSpecial = leftKey.startsWith(priorityCategory);
0250         bool rightIsSpecial = rightKey.startsWith(priorityCategory);
0251 
0252         return leftIsSpecial != rightIsSpecial ?
0253             leftIsSpecial : leftKey < rightKey;
0254     }
0255 
0256 private:
0257     TModel *m_model {nullptr};
0258 };
0259 
0260 #endif /* __KIS_CATEGORIZED_LIST_MODEL_H */