File indexing completed on 2024-05-26 05:14:40
0001 /* 0002 SPDX-FileCopyrightText: 2008 Ingo Klöcker <kloecker@kde.org> 0003 SPDX-FileCopyrightText: 2010-2024 Laurent Montel <montel@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "collectiondialog.h" 0009 0010 #include "asyncselectionhandler_p.h" 0011 0012 #include "collectioncreatejob.h" 0013 #include "collectionfetchscope.h" 0014 #include "collectionfilterproxymodel.h" 0015 #include "collectionutils.h" 0016 #include "entityrightsfiltermodel.h" 0017 #include "entitytreemodel.h" 0018 #include "entitytreeview.h" 0019 #include "monitor.h" 0020 #include "session.h" 0021 0022 #include <QCheckBox> 0023 #include <QDialogButtonBox> 0024 #include <QHeaderView> 0025 #include <QLabel> 0026 #include <QVBoxLayout> 0027 0028 #include <KConfig> 0029 #include <KConfigGroup> 0030 #include <KLineEditEventHandler> 0031 #include <KLocalizedString> 0032 #include <QInputDialog> 0033 #include <QLineEdit> 0034 #include <QMessageBox> 0035 #include <QPushButton> 0036 0037 using namespace Akonadi; 0038 0039 class Akonadi::CollectionDialogPrivate 0040 { 0041 public: 0042 CollectionDialogPrivate(QAbstractItemModel *customModel, CollectionDialog *parent, CollectionDialog::CollectionDialogOptions options) 0043 : mParent(parent) 0044 { 0045 // setup GUI 0046 auto layout = new QVBoxLayout(mParent); 0047 0048 mTextLabel = new QLabel(mParent); 0049 layout->addWidget(mTextLabel); 0050 mTextLabel->hide(); 0051 0052 auto filterCollectionLineEdit = new QLineEdit(mParent); 0053 KLineEditEventHandler::catchReturnKey(filterCollectionLineEdit); 0054 filterCollectionLineEdit->setClearButtonEnabled(true); 0055 filterCollectionLineEdit->setPlaceholderText( 0056 i18nc("@info Displayed grayed-out inside the " 0057 "textbox, verb to search", 0058 "Search")); 0059 layout->addWidget(filterCollectionLineEdit); 0060 0061 mView = new EntityTreeView(mParent); 0062 mView->setDragDropMode(QAbstractItemView::NoDragDrop); 0063 mView->header()->hide(); 0064 layout->addWidget(mView); 0065 0066 mUseByDefault = new QCheckBox(i18n("Use folder by default"), mParent); 0067 mUseByDefault->hide(); 0068 layout->addWidget(mUseByDefault); 0069 0070 mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, mParent); 0071 mParent->connect(mButtonBox, &QDialogButtonBox::accepted, mParent, &QDialog::accept); 0072 mParent->connect(mButtonBox, &QDialogButtonBox::rejected, mParent, &QDialog::reject); 0073 layout->addWidget(mButtonBox); 0074 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 0075 0076 // setup models 0077 QAbstractItemModel *baseModel = nullptr; 0078 0079 if (customModel) { 0080 baseModel = customModel; 0081 } else { 0082 mMonitor = new Akonadi::Monitor(mParent); 0083 mMonitor->setObjectName(QLatin1StringView("CollectionDialogMonitor")); 0084 mMonitor->fetchCollection(true); 0085 mMonitor->setCollectionMonitored(Akonadi::Collection::root()); 0086 0087 auto model = new EntityTreeModel(mMonitor, mParent); 0088 model->setItemPopulationStrategy(EntityTreeModel::NoItemPopulation); 0089 model->setListFilter(CollectionFetchScope::Display); 0090 baseModel = model; 0091 } 0092 0093 mMimeTypeFilterModel = new CollectionFilterProxyModel(mParent); 0094 mMimeTypeFilterModel->setSourceModel(baseModel); 0095 mMimeTypeFilterModel->setExcludeVirtualCollections(true); 0096 0097 mRightsFilterModel = new EntityRightsFilterModel(mParent); 0098 mRightsFilterModel->setSourceModel(mMimeTypeFilterModel); 0099 0100 mFilterCollection = new QSortFilterProxyModel(mParent); 0101 mFilterCollection->setRecursiveFilteringEnabled(true); 0102 mFilterCollection->setSourceModel(mRightsFilterModel); 0103 mFilterCollection->setFilterCaseSensitivity(Qt::CaseInsensitive); 0104 mView->setModel(mFilterCollection); 0105 0106 changeCollectionDialogOptions(options); 0107 mParent->connect(filterCollectionLineEdit, &QLineEdit::textChanged, mParent, [this](const QString &str) { 0108 slotFilterFixedString(str); 0109 }); 0110 0111 mParent->connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged, mParent, [this]() { 0112 slotSelectionChanged(); 0113 }); 0114 mParent->connect(mView, qOverload<const QModelIndex &>(&QAbstractItemView::doubleClicked), mParent, [this]() { 0115 slotDoubleClicked(); 0116 }); 0117 0118 mSelectionHandler = new AsyncSelectionHandler(mFilterCollection, mParent); 0119 mParent->connect(mSelectionHandler, &AsyncSelectionHandler::collectionAvailable, mParent, [this](const QModelIndex &index) { 0120 slotCollectionAvailable(index); 0121 }); 0122 readConfig(); 0123 } 0124 0125 ~CollectionDialogPrivate() 0126 { 0127 writeConfig(); 0128 } 0129 0130 void slotCollectionAvailable(const QModelIndex &index) 0131 { 0132 mView->expandAll(); 0133 mView->setCurrentIndex(index); 0134 } 0135 0136 void slotFilterFixedString(const QString &filter) 0137 { 0138 mFilterCollection->setFilterFixedString(filter); 0139 if (mKeepTreeExpanded) { 0140 mView->expandAll(); 0141 } 0142 } 0143 0144 void readConfig() 0145 { 0146 KConfig config(QStringLiteral("akonadi_contactrc")); 0147 KConfigGroup group(&config, QStringLiteral("CollectionDialog")); 0148 const QSize size = group.readEntry("Size", QSize(800, 500)); 0149 if (size.isValid()) { 0150 mParent->resize(size); 0151 } 0152 } 0153 0154 void writeConfig() const 0155 { 0156 KConfig config(QStringLiteral("akonadi_contactrc")); 0157 KConfigGroup group(&config, QStringLiteral("CollectionDialog")); 0158 group.writeEntry("Size", mParent->size()); 0159 group.sync(); 0160 } 0161 0162 CollectionDialog *const mParent; 0163 0164 Monitor *mMonitor = nullptr; 0165 CollectionFilterProxyModel *mMimeTypeFilterModel = nullptr; 0166 EntityRightsFilterModel *mRightsFilterModel = nullptr; 0167 EntityTreeView *mView = nullptr; 0168 AsyncSelectionHandler *mSelectionHandler = nullptr; 0169 QLabel *mTextLabel = nullptr; 0170 QSortFilterProxyModel *mFilterCollection = nullptr; 0171 QCheckBox *mUseByDefault = nullptr; 0172 QStringList mContentMimeTypes; 0173 QDialogButtonBox *mButtonBox = nullptr; 0174 QPushButton *mNewSubfolderButton = nullptr; 0175 bool mAllowToCreateNewChildCollection = false; 0176 bool mKeepTreeExpanded = false; 0177 0178 void slotDoubleClicked(); 0179 void slotSelectionChanged(); 0180 void slotAddChildCollection(); 0181 void slotCollectionCreationResult(KJob *job); 0182 bool canCreateCollection(const Akonadi::Collection &parentCollection) const; 0183 void changeCollectionDialogOptions(CollectionDialog::CollectionDialogOptions options); 0184 bool canSelectCollection() const; 0185 }; 0186 0187 void CollectionDialogPrivate::slotDoubleClicked() 0188 { 0189 if (canSelectCollection()) { 0190 mParent->accept(); 0191 } 0192 } 0193 0194 bool CollectionDialogPrivate::canSelectCollection() const 0195 { 0196 bool result = (!mView->selectionModel()->selectedIndexes().isEmpty()); 0197 if (mAllowToCreateNewChildCollection) { 0198 const Akonadi::Collection parentCollection = mParent->selectedCollection(); 0199 0200 if (parentCollection.isValid()) { 0201 result = (parentCollection.rights() & Akonadi::Collection::CanCreateItem); 0202 } 0203 } 0204 return result; 0205 } 0206 0207 void CollectionDialogPrivate::slotSelectionChanged() 0208 { 0209 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(!mView->selectionModel()->selectedIndexes().isEmpty()); 0210 if (mAllowToCreateNewChildCollection) { 0211 const Akonadi::Collection parentCollection = mParent->selectedCollection(); 0212 const bool canCreateChildCollections = canCreateCollection(parentCollection); 0213 0214 mNewSubfolderButton->setEnabled(canCreateChildCollections && !parentCollection.isVirtual()); 0215 if (parentCollection.isValid()) { 0216 const bool canCreateItems = (parentCollection.rights() & Akonadi::Collection::CanCreateItem); 0217 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(canCreateItems); 0218 } 0219 } 0220 } 0221 0222 void CollectionDialogPrivate::changeCollectionDialogOptions(CollectionDialog::CollectionDialogOptions options) 0223 { 0224 mAllowToCreateNewChildCollection = (options & CollectionDialog::AllowToCreateNewChildCollection); 0225 if (mAllowToCreateNewChildCollection) { 0226 mNewSubfolderButton = mButtonBox->addButton(i18n("&New Subfolder..."), QDialogButtonBox::NoRole); 0227 mNewSubfolderButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-new"))); 0228 mNewSubfolderButton->setToolTip(i18n("Create a new subfolder under the currently selected folder")); 0229 mNewSubfolderButton->setEnabled(false); 0230 QObject::connect(mNewSubfolderButton, &QPushButton::clicked, mParent, [this]() { 0231 slotAddChildCollection(); 0232 }); 0233 } 0234 mKeepTreeExpanded = (options & CollectionDialog::KeepTreeExpanded); 0235 if (mKeepTreeExpanded) { 0236 mParent->connect(mRightsFilterModel, &EntityRightsFilterModel::rowsInserted, mView, &EntityTreeView::expandAll, Qt::UniqueConnection); 0237 mView->expandAll(); 0238 } 0239 } 0240 0241 bool CollectionDialogPrivate::canCreateCollection(const Akonadi::Collection &parentCollection) const 0242 { 0243 if (!parentCollection.isValid()) { 0244 return false; 0245 } 0246 0247 if ((parentCollection.rights() & Akonadi::Collection::CanCreateCollection)) { 0248 const QStringList dialogMimeTypeFilter = mParent->mimeTypeFilter(); 0249 const QStringList parentCollectionMimeTypes = parentCollection.contentMimeTypes(); 0250 for (const QString &mimetype : dialogMimeTypeFilter) { 0251 if (parentCollectionMimeTypes.contains(mimetype)) { 0252 return true; 0253 } 0254 } 0255 return true; 0256 } 0257 return false; 0258 } 0259 0260 void CollectionDialogPrivate::slotAddChildCollection() 0261 { 0262 const Akonadi::Collection parentCollection = mParent->selectedCollection(); 0263 if (canCreateCollection(parentCollection)) { 0264 bool ok = false; 0265 const QString name = 0266 QInputDialog::getText(mParent, i18nc("@title:window", "New Folder"), i18nc("@label:textbox, name of a thing", "Name"), {}, {}, &ok); 0267 if (name.trimmed().isEmpty() || !ok) { 0268 return; 0269 } 0270 0271 Akonadi::Collection collection; 0272 collection.setName(name); 0273 collection.setParentCollection(parentCollection); 0274 if (!mContentMimeTypes.isEmpty()) { 0275 collection.setContentMimeTypes(mContentMimeTypes); 0276 } 0277 auto job = new Akonadi::CollectionCreateJob(collection); 0278 QObject::connect(job, &Akonadi::CollectionCreateJob::result, mParent, [this](KJob *job) { 0279 slotCollectionCreationResult(job); 0280 }); 0281 } 0282 } 0283 0284 void CollectionDialogPrivate::slotCollectionCreationResult(KJob *job) 0285 { 0286 if (job->error()) { 0287 QMessageBox::critical(mParent, i18nc("@title:window", "Folder Creation Failed"), i18n("Could not create folder: %1", job->errorString())); 0288 } 0289 } 0290 0291 CollectionDialog::CollectionDialog(QWidget *parent) 0292 : QDialog(parent) 0293 , d(new CollectionDialogPrivate(nullptr, this, CollectionDialog::None)) 0294 { 0295 } 0296 0297 CollectionDialog::CollectionDialog(QAbstractItemModel *model, QWidget *parent) 0298 : QDialog(parent) 0299 , d(new CollectionDialogPrivate(model, this, CollectionDialog::None)) 0300 { 0301 } 0302 0303 CollectionDialog::CollectionDialog(CollectionDialogOptions options, QAbstractItemModel *model, QWidget *parent) 0304 : QDialog(parent) 0305 , d(new CollectionDialogPrivate(model, this, options)) 0306 { 0307 } 0308 0309 CollectionDialog::~CollectionDialog() = default; 0310 0311 Akonadi::Collection CollectionDialog::selectedCollection() const 0312 { 0313 if (selectionMode() == QAbstractItemView::SingleSelection) { 0314 const QModelIndex index = d->mView->currentIndex(); 0315 if (index.isValid()) { 0316 return index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>(); 0317 } 0318 } 0319 0320 return Collection(); 0321 } 0322 0323 Akonadi::Collection::List CollectionDialog::selectedCollections() const 0324 { 0325 Collection::List collections; 0326 const QItemSelectionModel *selectionModel = d->mView->selectionModel(); 0327 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes(); 0328 for (const QModelIndex &index : selectedIndexes) { 0329 if (index.isValid()) { 0330 const auto collection = index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>(); 0331 if (collection.isValid()) { 0332 collections.append(collection); 0333 } 0334 } 0335 } 0336 0337 return collections; 0338 } 0339 0340 void CollectionDialog::setMimeTypeFilter(const QStringList &mimeTypes) 0341 { 0342 if (mimeTypeFilter() == mimeTypes) { 0343 return; 0344 } 0345 0346 d->mMimeTypeFilterModel->clearFilters(); 0347 d->mMimeTypeFilterModel->addMimeTypeFilters(mimeTypes); 0348 0349 if (d->mMonitor) { 0350 for (const QString &mimetype : mimeTypes) { 0351 d->mMonitor->setMimeTypeMonitored(mimetype); 0352 } 0353 } 0354 } 0355 0356 QStringList CollectionDialog::mimeTypeFilter() const 0357 { 0358 return d->mMimeTypeFilterModel->mimeTypeFilters(); 0359 } 0360 0361 void CollectionDialog::setAccessRightsFilter(Collection::Rights rights) 0362 { 0363 if (accessRightsFilter() == rights) { 0364 return; 0365 } 0366 d->mRightsFilterModel->setAccessRights(rights); 0367 } 0368 0369 Akonadi::Collection::Rights CollectionDialog::accessRightsFilter() const 0370 { 0371 return d->mRightsFilterModel->accessRights(); 0372 } 0373 0374 void CollectionDialog::setDescription(const QString &text) 0375 { 0376 d->mTextLabel->setText(text); 0377 d->mTextLabel->show(); 0378 } 0379 0380 void CollectionDialog::setDefaultCollection(const Collection &collection) 0381 { 0382 d->mSelectionHandler->waitForCollection(collection); 0383 } 0384 0385 void CollectionDialog::setSelectionMode(QAbstractItemView::SelectionMode mode) 0386 { 0387 d->mView->setSelectionMode(mode); 0388 } 0389 0390 QAbstractItemView::SelectionMode CollectionDialog::selectionMode() const 0391 { 0392 return d->mView->selectionMode(); 0393 } 0394 0395 void CollectionDialog::changeCollectionDialogOptions(CollectionDialogOptions options) 0396 { 0397 d->changeCollectionDialogOptions(options); 0398 } 0399 0400 void CollectionDialog::setUseFolderByDefault(bool b) 0401 { 0402 d->mUseByDefault->setChecked(b); 0403 d->mUseByDefault->show(); 0404 } 0405 0406 bool CollectionDialog::useFolderByDefault() const 0407 { 0408 return d->mUseByDefault->isChecked(); 0409 } 0410 0411 void CollectionDialog::setContentMimeTypes(const QStringList &mimetypes) 0412 { 0413 d->mContentMimeTypes = mimetypes; 0414 } 0415 0416 #include "moc_collectiondialog.cpp"