Akonadi

collectiondialog.cpp
1/*
2 SPDX-FileCopyrightText: 2008 Ingo Klöcker <kloecker@kde.org>
3 SPDX-FileCopyrightText: 2010-2024 Laurent Montel <montel@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "collectiondialog.h"
9
10#include "asyncselectionhandler_p.h"
11
12#include "collectioncreatejob.h"
13#include "collectionfetchscope.h"
14#include "collectionfilterproxymodel.h"
15#include "collectionutils.h"
16#include "entityrightsfiltermodel.h"
17#include "entitytreemodel.h"
18#include "entitytreeview.h"
19#include "monitor.h"
20#include "session.h"
21
22#include <QCheckBox>
23#include <QDialogButtonBox>
24#include <QHeaderView>
25#include <QLabel>
26#include <QVBoxLayout>
27
28#include <KConfig>
29#include <KConfigGroup>
30#include <KLineEditEventHandler>
31#include <KLocalizedString>
32#include <QInputDialog>
33#include <QLineEdit>
34#include <QMessageBox>
35#include <QPushButton>
36
37using namespace Akonadi;
38
39class Akonadi::CollectionDialogPrivate
40{
41public:
42 CollectionDialogPrivate(QAbstractItemModel *customModel, CollectionDialog *parent, CollectionDialog::CollectionDialogOptions options)
43 : mParent(parent)
44 {
45 // setup GUI
46 auto layout = new QVBoxLayout(mParent);
47
48 mTextLabel = new QLabel(mParent);
49 layout->addWidget(mTextLabel);
50 mTextLabel->hide();
51
52 auto filterCollectionLineEdit = new QLineEdit(mParent);
53 KLineEditEventHandler::catchReturnKey(filterCollectionLineEdit);
54 filterCollectionLineEdit->setClearButtonEnabled(true);
55 filterCollectionLineEdit->setPlaceholderText(
56 i18nc("@info Displayed grayed-out inside the "
57 "textbox, verb to search",
58 "Search"));
59 layout->addWidget(filterCollectionLineEdit);
60
61 mView = new EntityTreeView(mParent);
63 mView->header()->hide();
64 layout->addWidget(mView);
65
66 mUseByDefault = new QCheckBox(i18n("Use folder by default"), mParent);
67 mUseByDefault->hide();
68 layout->addWidget(mUseByDefault);
69
71 mParent->connect(mButtonBox, &QDialogButtonBox::accepted, mParent, &QDialog::accept);
72 mParent->connect(mButtonBox, &QDialogButtonBox::rejected, mParent, &QDialog::reject);
73 layout->addWidget(mButtonBox);
74 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
75
76 // setup models
77 QAbstractItemModel *baseModel = nullptr;
78
79 if (customModel) {
80 baseModel = customModel;
81 } else {
82 mMonitor = new Akonadi::Monitor(mParent);
83 mMonitor->setObjectName(QLatin1StringView("CollectionDialogMonitor"));
84 mMonitor->fetchCollection(true);
86
87 auto model = new EntityTreeModel(mMonitor, mParent);
88 model->setItemPopulationStrategy(EntityTreeModel::NoItemPopulation);
89 model->setListFilter(CollectionFetchScope::Display);
90 baseModel = model;
91 }
92
93 mMimeTypeFilterModel = new CollectionFilterProxyModel(mParent);
94 mMimeTypeFilterModel->setSourceModel(baseModel);
95 mMimeTypeFilterModel->setExcludeVirtualCollections(true);
96
97 mRightsFilterModel = new EntityRightsFilterModel(mParent);
98 mRightsFilterModel->setSourceModel(mMimeTypeFilterModel);
99
100 mFilterCollection = new QSortFilterProxyModel(mParent);
101 mFilterCollection->setRecursiveFilteringEnabled(true);
102 mFilterCollection->setSourceModel(mRightsFilterModel);
104 mView->setModel(mFilterCollection);
105
106 changeCollectionDialogOptions(options);
107 mParent->connect(filterCollectionLineEdit, &QLineEdit::textChanged, mParent, [this](const QString &str) {
108 slotFilterFixedString(str);
109 });
110
111 mParent->connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged, mParent, [this]() {
112 slotSelectionChanged();
113 });
114 mParent->connect(mView, qOverload<const QModelIndex &>(&QAbstractItemView::doubleClicked), mParent, [this]() {
115 slotDoubleClicked();
116 });
117
118 mSelectionHandler = new AsyncSelectionHandler(mFilterCollection, mParent);
119 mParent->connect(mSelectionHandler, &AsyncSelectionHandler::collectionAvailable, mParent, [this](const QModelIndex &index) {
120 slotCollectionAvailable(index);
121 });
122 readConfig();
123 }
124
125 ~CollectionDialogPrivate()
126 {
127 writeConfig();
128 }
129
130 void slotCollectionAvailable(const QModelIndex &index)
131 {
132 mView->expandAll();
133 mView->setCurrentIndex(index);
134 }
135
136 void slotFilterFixedString(const QString &filter)
137 {
138 mFilterCollection->setFilterFixedString(filter);
139 if (mKeepTreeExpanded) {
140 mView->expandAll();
141 }
142 }
143
144 void readConfig()
145 {
146 KConfig config(QStringLiteral("akonadi_contactrc"));
147 KConfigGroup group(&config, QStringLiteral("CollectionDialog"));
148 const QSize size = group.readEntry("Size", QSize(800, 500));
149 if (size.isValid()) {
150 mParent->resize(size);
151 }
152 }
153
154 void writeConfig() const
155 {
156 KConfig config(QStringLiteral("akonadi_contactrc"));
157 KConfigGroup group(&config, QStringLiteral("CollectionDialog"));
158 group.writeEntry("Size", mParent->size());
159 group.sync();
160 }
161
162 CollectionDialog *const mParent;
163
164 Monitor *mMonitor = nullptr;
165 CollectionFilterProxyModel *mMimeTypeFilterModel = nullptr;
166 EntityRightsFilterModel *mRightsFilterModel = nullptr;
167 EntityTreeView *mView = nullptr;
168 AsyncSelectionHandler *mSelectionHandler = nullptr;
169 QLabel *mTextLabel = nullptr;
170 QSortFilterProxyModel *mFilterCollection = nullptr;
171 QCheckBox *mUseByDefault = nullptr;
172 QStringList mContentMimeTypes;
173 QDialogButtonBox *mButtonBox = nullptr;
174 QPushButton *mNewSubfolderButton = nullptr;
175 bool mAllowToCreateNewChildCollection = false;
176 bool mKeepTreeExpanded = false;
177
178 void slotDoubleClicked();
179 void slotSelectionChanged();
180 void slotAddChildCollection();
181 void slotCollectionCreationResult(KJob *job);
182 bool canCreateCollection(const Akonadi::Collection &parentCollection) const;
183 void changeCollectionDialogOptions(CollectionDialog::CollectionDialogOptions options);
184 bool canSelectCollection() const;
185};
186
187void CollectionDialogPrivate::slotDoubleClicked()
188{
189 if (canSelectCollection()) {
190 mParent->accept();
191 }
192}
193
194bool CollectionDialogPrivate::canSelectCollection() const
195{
196 bool result = (!mView->selectionModel()->selectedIndexes().isEmpty());
197 if (mAllowToCreateNewChildCollection) {
198 const Akonadi::Collection parentCollection = mParent->selectedCollection();
199
200 if (parentCollection.isValid()) {
201 result = (parentCollection.rights() & Akonadi::Collection::CanCreateItem);
202 }
203 }
204 return result;
205}
206
207void CollectionDialogPrivate::slotSelectionChanged()
208{
209 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(!mView->selectionModel()->selectedIndexes().isEmpty());
210 if (mAllowToCreateNewChildCollection) {
211 const Akonadi::Collection parentCollection = mParent->selectedCollection();
212 const bool canCreateChildCollections = canCreateCollection(parentCollection);
213
214 mNewSubfolderButton->setEnabled(canCreateChildCollections && !parentCollection.isVirtual());
215 if (parentCollection.isValid()) {
216 const bool canCreateItems = (parentCollection.rights() & Akonadi::Collection::CanCreateItem);
217 mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(canCreateItems);
218 }
219 }
220}
221
222void CollectionDialogPrivate::changeCollectionDialogOptions(CollectionDialog::CollectionDialogOptions options)
223{
224 mAllowToCreateNewChildCollection = (options & CollectionDialog::AllowToCreateNewChildCollection);
225 if (mAllowToCreateNewChildCollection) {
226 mNewSubfolderButton = mButtonBox->addButton(i18nc("@action:button", "&New Subfolder..."), QDialogButtonBox::NoRole);
227 mNewSubfolderButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
228 mNewSubfolderButton->setToolTip(i18nc("@info:tooltip", "Create a new subfolder under the currently selected folder"));
229 mNewSubfolderButton->setEnabled(false);
230 QObject::connect(mNewSubfolderButton, &QPushButton::clicked, mParent, [this]() {
231 slotAddChildCollection();
232 });
233 }
234 mKeepTreeExpanded = (options & CollectionDialog::KeepTreeExpanded);
235 if (mKeepTreeExpanded) {
236 mParent->connect(mRightsFilterModel, &EntityRightsFilterModel::rowsInserted, mView, &EntityTreeView::expandAll, Qt::UniqueConnection);
237 mView->expandAll();
238 }
239}
240
241bool CollectionDialogPrivate::canCreateCollection(const Akonadi::Collection &parentCollection) const
242{
243 if (!parentCollection.isValid()) {
244 return false;
245 }
246
247 if ((parentCollection.rights() & Akonadi::Collection::CanCreateCollection)) {
248 const QStringList dialogMimeTypeFilter = mParent->mimeTypeFilter();
249 const QStringList parentCollectionMimeTypes = parentCollection.contentMimeTypes();
250 for (const QString &mimetype : dialogMimeTypeFilter) {
251 if (parentCollectionMimeTypes.contains(mimetype)) {
252 return true;
253 }
254 }
255 return true;
256 }
257 return false;
258}
259
260void CollectionDialogPrivate::slotAddChildCollection()
261{
262 const Akonadi::Collection parentCollection = mParent->selectedCollection();
263 if (canCreateCollection(parentCollection)) {
264 bool ok = false;
265 const QString name =
266 QInputDialog::getText(mParent, i18nc("@title:window", "New Folder"), i18nc("@label:textbox, name of a thing", "Name"), {}, {}, &ok);
267 if (name.trimmed().isEmpty() || !ok) {
268 return;
269 }
270
271 Akonadi::Collection collection;
272 collection.setName(name);
273 collection.setParentCollection(parentCollection);
274 if (!mContentMimeTypes.isEmpty()) {
275 collection.setContentMimeTypes(mContentMimeTypes);
276 }
277 auto job = new Akonadi::CollectionCreateJob(collection);
278 QObject::connect(job, &Akonadi::CollectionCreateJob::result, mParent, [this](KJob *job) {
279 slotCollectionCreationResult(job);
280 });
281 }
282}
283
284void CollectionDialogPrivate::slotCollectionCreationResult(KJob *job)
285{
286 if (job->error()) {
287 QMessageBox::critical(mParent, i18nc("@title:window", "Folder Creation Failed"), i18n("Could not create folder: %1", job->errorString()));
288 }
289}
290
292 : QDialog(parent)
293 , d(new CollectionDialogPrivate(nullptr, this, CollectionDialog::None))
294{
295}
296
298 : QDialog(parent)
299 , d(new CollectionDialogPrivate(model, this, CollectionDialog::None))
300{
301}
302
304 : QDialog(parent)
305 , d(new CollectionDialogPrivate(model, this, options))
306{
307}
308
310
312{
314 const QModelIndex index = d->mView->currentIndex();
315 if (index.isValid()) {
316 return index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
317 }
318 }
319
320 return Collection();
321}
322
324{
325 Collection::List collections;
326 const QItemSelectionModel *selectionModel = d->mView->selectionModel();
327 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
328 for (const QModelIndex &index : selectedIndexes) {
329 if (index.isValid()) {
330 const auto collection = index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
331 if (collection.isValid()) {
332 collections.append(collection);
333 }
334 }
335 }
336
337 return collections;
338}
339
341{
342 if (mimeTypeFilter() == mimeTypes) {
343 return;
344 }
345
346 d->mMimeTypeFilterModel->clearFilters();
347 d->mMimeTypeFilterModel->addMimeTypeFilters(mimeTypes);
348
349 if (d->mMonitor) {
350 for (const QString &mimetype : mimeTypes) {
351 d->mMonitor->setMimeTypeMonitored(mimetype);
352 }
353 }
354}
355
357{
358 return d->mMimeTypeFilterModel->mimeTypeFilters();
359}
360
362{
363 if (accessRightsFilter() == rights) {
364 return;
365 }
366 d->mRightsFilterModel->setAccessRights(rights);
367}
368
370{
371 return d->mRightsFilterModel->accessRights();
372}
373
375{
376 d->mTextLabel->setText(text);
377 d->mTextLabel->show();
378}
379
381{
382 d->mSelectionHandler->waitForCollection(collection);
383}
384
386{
387 d->mView->setSelectionMode(mode);
388}
389
391{
392 return d->mView->selectionMode();
393}
394
396{
397 d->changeCollectionDialogOptions(options);
398}
399
401{
402 d->mUseByDefault->setChecked(b);
403 d->mUseByDefault->show();
404}
405
407{
408 return d->mUseByDefault->isChecked();
409}
410
412{
413 d->mContentMimeTypes = mimetypes;
414}
415
416#include "moc_collectiondialog.cpp"
Job that creates a new collection in the Akonadi storage.
A collection selection dialog.
void setContentMimeTypes(const QStringList &mimetypes)
Allow to specify collection content mimetype when we create new one.
Akonadi::Collection selectedCollection() const
Returns the selected collection if the selection mode is QAbstractItemView::SingleSelection.
~CollectionDialog() override
Destroys the collection dialog.
void changeCollectionDialogOptions(CollectionDialogOptions options)
Change collection dialog options.
QAbstractItemView::SelectionMode selectionMode() const
Returns the selection mode.
void setDefaultCollection(const Collection &collection)
Sets the collection that shall be selected by default.
void setSelectionMode(QAbstractItemView::SelectionMode mode)
Sets the selection mode.
void setAccessRightsFilter(Collection::Rights rights)
Sets the access rights that the listed collections shall match with.
QStringList mimeTypeFilter() const
Returns the mime types any of which the selected collection(s) shall support.
void setMimeTypeFilter(const QStringList &mimeTypes)
Sets the mime types any of which the selected collection(s) shall support.
void setDescription(const QString &text)
Sets the text that will be shown in the dialog.
CollectionDialog(QWidget *parent=nullptr)
Creates a new collection dialog.
Collection::Rights accessRightsFilter() const
Sets the access rights that the listed collections shall match with.
Akonadi::Collection::List selectedCollections() const
Returns the list of selected collections.
@ Display
Only retrieve collections for display, taking the local preference and enabled into account.
A proxy model that filters collections by mime type.
void setExcludeVirtualCollections(bool exclude)
Sets whether we want virtual collections to be filtered or not.
Represents a collection of PIM items.
Definition collection.h:62
void setParentCollection(const Collection &parent)
Set the parent collection of this object.
static Collection root()
Returns the root collection.
void setName(const QString &name)
Sets the i18n'ed name of the collection.
@ CanCreateItem
Can create new items in this collection.
Definition collection.h:92
@ CanCreateCollection
Can create new subcollections in this collection.
Definition collection.h:95
void setContentMimeTypes(const QStringList &types)
Sets the list of possible content mime types.
A proxy model that filters entities by access rights.
A model for collections and items together.
@ NoItemPopulation
Do not include items in the model.
@ CollectionRole
The collection.
A view to show an item/collection tree provided by an EntityTreeModel.
void setModel(QAbstractItemModel *model) override
Monitors an item or collection for changes.
Definition monitor.h:72
void setCollectionMonitored(const Collection &collection, bool monitored=true)
Sets whether the specified collection shall be monitored for changes.
Definition monitor.cpp:48
void fetchCollection(bool enable)
Enables automatic fetching of changed collections from the Akonadi storage.
Definition monitor.cpp:193
virtual QString errorString() const
int error() const
void result(KJob *job)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
QString name(GameStandardAction id)
void catchReturnKey(QObject *lineEdit)
void clicked(bool checked)
void setIcon(const QIcon &icon)
virtual QVariant data(const QModelIndex &index, int role) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
void doubleClicked(const QModelIndex &index)
void setDragDropMode(DragDropMode behavior)
QItemSelectionModel * selectionModel() const const
void setCurrentIndex(const QModelIndex &index)
virtual void accept()
virtual void reject()
QPushButton * addButton(StandardButton button)
QPushButton * button(StandardButton which) const const
QIcon fromTheme(const QString &name)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
void textChanged(const QString &text)
void append(QList< T > &&value)
bool isEmpty() const const
StandardButton critical(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
bool isValid() const const
const QAbstractItemModel * model() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setObjectName(QAnyStringView name)
bool isValid() const const
void setFilterCaseSensitivity(Qt::CaseSensitivity cs)
void setRecursiveFilteringEnabled(bool recursive)
void setFilterFixedString(const QString &pattern)
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
bool isEmpty() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
UniqueConnection
void expandAll()
QHeaderView * header() const const
T value() const const
void setEnabled(bool)
void hide()
void setToolTip(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 7 2024 11:56:45 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.