Akonadi

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

KDE's Doxygen guidelines are available online.