File indexing completed on 2024-09-22 04:41:01

0001 /*
0002     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB,
0003         a KDAB Group company, info@kdab.net
0004     SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "entityorderproxymodel.h"
0010 
0011 #include <QMimeData>
0012 
0013 #include <KConfigGroup>
0014 #include <QUrl>
0015 
0016 #include "entitytreemodel.h"
0017 #include "item.h"
0018 
0019 namespace Akonadi
0020 {
0021 class EntityOrderProxyModelPrivate
0022 {
0023 public:
0024     explicit EntityOrderProxyModelPrivate(EntityOrderProxyModel *qq)
0025         : q_ptr(qq)
0026     {
0027     }
0028 
0029     void saveOrder(const QModelIndex &index);
0030 
0031     KConfigGroup m_orderConfig;
0032 
0033     Q_DECLARE_PUBLIC(EntityOrderProxyModel)
0034     EntityOrderProxyModel *const q_ptr;
0035 };
0036 
0037 } // namespace Akonadi
0038 
0039 using namespace Akonadi;
0040 
0041 EntityOrderProxyModel::EntityOrderProxyModel(QObject *parent)
0042     : QSortFilterProxyModel(parent)
0043     , d_ptr(new EntityOrderProxyModelPrivate(this))
0044 {
0045     setRecursiveFilteringEnabled(true);
0046     setDynamicSortFilter(true);
0047     // setSortCaseSensitivity( Qt::CaseInsensitive );
0048 }
0049 
0050 EntityOrderProxyModel::~EntityOrderProxyModel() = default;
0051 
0052 void EntityOrderProxyModel::setOrderConfig(const KConfigGroup &configGroup)
0053 {
0054     Q_D(EntityOrderProxyModel);
0055     Q_EMIT layoutAboutToBeChanged();
0056     d->m_orderConfig = configGroup;
0057     Q_EMIT layoutChanged();
0058 }
0059 
0060 // reimplemented in FavoriteCollectionOrderProxyModel
0061 Collection EntityOrderProxyModel::parentCollection(const QModelIndex &index) const
0062 {
0063     return index.data(EntityTreeModel::ParentCollectionRole).value<Collection>();
0064 }
0065 
0066 static QString configKey(const Collection &col)
0067 {
0068     return !col.isValid() ? QStringLiteral("0") : QString::number(col.id());
0069 }
0070 
0071 bool EntityOrderProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0072 {
0073     Q_D(const EntityOrderProxyModel);
0074 
0075     if (!d->m_orderConfig.isValid()) {
0076         return QSortFilterProxyModel::lessThan(left, right);
0077     }
0078     const Collection col = parentCollection(left);
0079 
0080     const QStringList list = d->m_orderConfig.readEntry(configKey(col), QStringList());
0081 
0082     if (list.isEmpty()) {
0083         return QSortFilterProxyModel::lessThan(left, right);
0084     }
0085 
0086     const QString leftValue = configString(left);
0087     const QString rightValue = configString(right);
0088 
0089     const int leftPosition = list.indexOf(leftValue);
0090     const int rightPosition = list.indexOf(rightValue);
0091 
0092     if (leftPosition < 0 || rightPosition < 0) {
0093         return QSortFilterProxyModel::lessThan(left, right);
0094     }
0095 
0096     return leftPosition < rightPosition;
0097 }
0098 
0099 QStringList EntityOrderProxyModel::configStringsForDroppedUrls(const QList<QUrl> &urls, const Akonadi::Collection &parentCol, bool *containsMove) const
0100 {
0101     QStringList droppedList;
0102     droppedList.reserve(urls.count());
0103     for (const QUrl &url : urls) {
0104         const Collection col = Collection::fromUrl(url);
0105 
0106         if (!col.isValid()) {
0107             Item item = Item::fromUrl(url);
0108             if (!item.isValid()) {
0109                 continue;
0110             }
0111 
0112             const QModelIndexList list = EntityTreeModel::modelIndexesForItem(this, item);
0113             if (list.isEmpty()) {
0114                 continue;
0115             }
0116 
0117             if (!*containsMove && parentCollection(list.first()).id() != parentCol.id()) {
0118                 *containsMove = true;
0119             }
0120 
0121             droppedList << configString(list.first());
0122         } else {
0123             const QModelIndex idx = EntityTreeModel::modelIndexForCollection(this, col);
0124             if (!idx.isValid()) {
0125                 continue;
0126             }
0127 
0128             if (!*containsMove && parentCollection(idx).id() != parentCol.id()) {
0129                 *containsMove = true;
0130             }
0131 
0132             droppedList << configString(idx);
0133         }
0134     }
0135     return droppedList;
0136 }
0137 
0138 bool EntityOrderProxyModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
0139 {
0140     Q_D(EntityOrderProxyModel);
0141 
0142     if (!d->m_orderConfig.isValid()) {
0143         return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0144     }
0145 
0146     if (!data->hasFormat(QStringLiteral("text/uri-list"))) {
0147         return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0148     }
0149 
0150     if (row == -1) {
0151         return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0152     }
0153 
0154     const QList<QUrl> urls = data->urls();
0155     if (urls.isEmpty()) {
0156         return false;
0157     }
0158 
0159     Collection parentCol;
0160 
0161     if (parent.isValid()) {
0162         parentCol = parent.data(EntityTreeModel::CollectionRole).value<Collection>();
0163     } else {
0164         if (!hasChildren(parent)) {
0165             return QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0166         }
0167 
0168         const QModelIndex targetIndex = index(0, column, parent);
0169         parentCol = parentCollection(targetIndex);
0170     }
0171 
0172     bool containsMove = false;
0173     QStringList droppedList = configStringsForDroppedUrls(urls, parentCol, &containsMove);
0174 
0175     // Dropping new favorite folders
0176     if (droppedList.isEmpty()) {
0177         const bool ok = QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0178         if (ok) {
0179             droppedList = configStringsForDroppedUrls(urls, parentCol, &containsMove);
0180         }
0181     }
0182 
0183     QStringList existingList;
0184     if (d->m_orderConfig.hasKey(QString::number(parentCol.id()))) {
0185         existingList = d->m_orderConfig.readEntry(configKey(parentCol), QStringList());
0186     } else {
0187         const int rowCount = this->rowCount(parent);
0188         existingList.reserve(rowCount);
0189         for (int row = 0; row < rowCount; ++row) {
0190             static const int column = 0;
0191             const QModelIndex idx = this->index(row, column, parent);
0192             existingList.append(configString(idx));
0193         }
0194     }
0195     const int numberOfDroppedElement(droppedList.size());
0196     for (int i = 0; i < numberOfDroppedElement; ++i) {
0197         const QString &droppedItem = droppedList.at(i);
0198         const int existingIndex = existingList.indexOf(droppedItem);
0199         existingList.removeAt(existingIndex);
0200         existingList.insert(row + i - (existingIndex > row ? 0 : 1), droppedList.at(i));
0201     }
0202 
0203     d->m_orderConfig.writeEntry(configKey(parentCol), existingList);
0204 
0205     if (containsMove) {
0206         bool result = QSortFilterProxyModel::dropMimeData(data, action, row, column, parent);
0207         invalidate();
0208         return result;
0209     }
0210     invalidate();
0211     return true;
0212 }
0213 
0214 QModelIndexList EntityOrderProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
0215 {
0216     if (role < Qt::UserRole) {
0217         return QSortFilterProxyModel::match(start, role, value, hits, flags);
0218     }
0219 
0220     QModelIndexList list;
0221     QModelIndex proxyIndex;
0222     const auto matches = sourceModel()->match(mapToSource(start), role, value, hits, flags);
0223     for (const auto &idx : matches) {
0224         proxyIndex = mapFromSource(idx);
0225         if (proxyIndex.isValid()) {
0226             list.push_back(proxyIndex);
0227         }
0228     }
0229 
0230     return list;
0231 }
0232 
0233 void EntityOrderProxyModelPrivate::saveOrder(const QModelIndex &parent)
0234 {
0235     Q_Q(const EntityOrderProxyModel);
0236     int rowCount = q->rowCount(parent);
0237 
0238     if (rowCount == 0) {
0239         return;
0240     }
0241 
0242     static const int column = 0;
0243     QModelIndex childIndex = q->index(0, column, parent);
0244 
0245     const QString parentKey = q->parentConfigString(childIndex);
0246 
0247     if (parentKey.isEmpty()) {
0248         return;
0249     }
0250 
0251     QStringList list;
0252 
0253     list << q->configString(childIndex);
0254     saveOrder(childIndex);
0255     list.reserve(list.count() + rowCount);
0256     for (int row = 1; row < rowCount; ++row) {
0257         childIndex = q->index(row, column, parent);
0258         list << q->configString(childIndex);
0259         saveOrder(childIndex);
0260     }
0261 
0262     m_orderConfig.writeEntry(parentKey, list);
0263 }
0264 
0265 QString EntityOrderProxyModel::parentConfigString(const QModelIndex &index) const
0266 {
0267     const Collection col = parentCollection(index);
0268 
0269     Q_ASSERT(col.isValid());
0270     if (!col.isValid()) {
0271         return QString();
0272     }
0273 
0274     return QString::number(col.id());
0275 }
0276 
0277 QString EntityOrderProxyModel::configString(const QModelIndex &index) const
0278 {
0279     Item::Id iId = index.data(EntityTreeModel::ItemIdRole).toLongLong();
0280     if (iId != -1) {
0281         return QLatin1Char('i') + QString::number(iId);
0282     }
0283     Collection::Id cId = index.data(EntityTreeModel::CollectionIdRole).toLongLong();
0284     if (cId != -1) {
0285         return QLatin1Char('c') + QString::number(cId);
0286     }
0287     Q_ASSERT(!"Invalid entity");
0288     return QString();
0289 }
0290 
0291 void EntityOrderProxyModel::saveOrder()
0292 {
0293     Q_D(EntityOrderProxyModel);
0294     d->saveOrder(QModelIndex());
0295     d->m_orderConfig.sync();
0296 }
0297 
0298 void EntityOrderProxyModel::clearOrder(const QModelIndex &parent)
0299 {
0300     Q_D(EntityOrderProxyModel);
0301 
0302     const QString parentKey = parentConfigString(index(0, 0, parent));
0303 
0304     if (parentKey.isEmpty()) {
0305         return;
0306     }
0307 
0308     d->m_orderConfig.deleteEntry(parentKey);
0309     invalidate();
0310 }
0311 
0312 void EntityOrderProxyModel::clearTreeOrder()
0313 {
0314     Q_D(EntityOrderProxyModel);
0315     d->m_orderConfig.deleteGroup();
0316     invalidate();
0317 }
0318 
0319 #include "moc_entityorderproxymodel.cpp"