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 */