1/*
2 Copyright 2008 Ingo Klöcker <kloecker@kde.org>
3 Copyright 2010 Laurent Montel <montel@kde.org>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20
21#include "collectiondialog.h"
22
23#include "asyncselectionhandler_p.h"
24
25#include <akonadi/changerecorder.h>
26#include <akonadi/collectionfetchscope.h>
27#include <akonadi/collectionfilterproxymodel.h>
28#include <akonadi/entityrightsfiltermodel.h>
29#include <akonadi/entitytreemodel.h>
30#include <akonadi/entitytreeview.h>
31#include <akonadi/session.h>
32#include <akonadi/collectioncreatejob.h>
33#include <akonadi/collectionutils_p.h>
34
35#include <QHeaderView>
36#include <QLabel>
37#include <QVBoxLayout>
38#include <QCheckBox>
39
40#include <KLineEdit>
41#include <KLocalizedString>
42#include <KInputDialog>
43#include <KMessageBox>
44
45using namespace Akonadi;
46
47class CollectionDialog::Private
48{
49public:
50 Private(QAbstractItemModel *customModel, CollectionDialog *parent, CollectionDialogOptions options)
51 : mParent(parent)
52 , mMonitor(0)
53 {
54 // setup GUI
55 QWidget *widget = mParent->mainWidget();
56 QVBoxLayout *layout = new QVBoxLayout(widget);
57 layout->setContentsMargins(0, 0, 0, 0);
58
59 mTextLabel = new QLabel;
60 layout->addWidget(mTextLabel);
61 mTextLabel->hide();
62
63 KLineEdit *filterCollectionLineEdit = new KLineEdit(widget);
64 filterCollectionLineEdit->setClearButtonShown(true);
65 filterCollectionLineEdit->setClickMessage(i18nc("@info/plain Displayed grayed-out inside the "
66 "textbox, verb to search", "Search"));
67 layout->addWidget(filterCollectionLineEdit);
68
69 mView = new EntityTreeView;
70 mView->setDragDropMode(QAbstractItemView::NoDragDrop);
71 mView->header()->hide();
72 layout->addWidget(mView);
73
74 mUseByDefault = new QCheckBox(i18n("Use folder by default"));
75 mUseByDefault->hide();
76 layout->addWidget(mUseByDefault);
77
78 mParent->enableButton(KDialog::Ok, false);
79
80 // setup models
81 QAbstractItemModel *baseModel;
82
83 if (customModel) {
84 baseModel = customModel;
85 } else {
86 mMonitor = new Akonadi::ChangeRecorder(mParent);
87 mMonitor->fetchCollection(true);
88 mMonitor->setCollectionMonitored(Akonadi::Collection::root());
89
90 EntityTreeModel *model = new EntityTreeModel(mMonitor, mParent);
91 model->setItemPopulationStrategy(EntityTreeModel::NoItemPopulation);
92 baseModel = model;
93 }
94
95 mMimeTypeFilterModel = new CollectionFilterProxyModel(mParent);
96 mMimeTypeFilterModel->setSourceModel(baseModel);
97 mMimeTypeFilterModel->setExcludeVirtualCollections(true);
98
99 mRightsFilterModel = new EntityRightsFilterModel(mParent);
100 mRightsFilterModel->setSourceModel(mMimeTypeFilterModel);
101
102 mFilterCollection = new KRecursiveFilterProxyModel(mParent);
103 mFilterCollection->setDynamicSortFilter(true);
104 mFilterCollection->setSourceModel(mRightsFilterModel);
105 mFilterCollection->setFilterCaseSensitivity(Qt::CaseInsensitive);
106 mView->setModel(mFilterCollection);
107
108 changeCollectionDialogOptions(options);
109 mParent->connect(filterCollectionLineEdit, SIGNAL(textChanged(QString)),
110 mParent, SLOT(slotFilterFixedString(QString)));
111
112 mParent->connect(mView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
113 mParent, SLOT(slotSelectionChanged()));
114
115 mParent->connect(mView, SIGNAL(doubleClicked(QModelIndex)),
116 mParent, SLOT(accept()));
117
118 mSelectionHandler = new AsyncSelectionHandler(mFilterCollection, mParent);
119 mParent->connect(mSelectionHandler, SIGNAL(collectionAvailable(QModelIndex)),
120 mParent, SLOT(slotCollectionAvailable(QModelIndex)));
121 readConfig();
122 }
123
124 ~Private()
125 {
126 writeConfig();
127 }
128
129 void slotCollectionAvailable(const QModelIndex &index)
130 {
131 mView->expandAll();
132 mView->setCurrentIndex(index);
133 }
134
135 void slotFilterFixedString(const QString &filter)
136 {
137 mFilterCollection->setFilterFixedString(filter);
138 if (mKeepTreeExpanded) {
139 mView->expandAll();
140 }
141 }
142
143 void readConfig()
144 {
145 KConfig config( QLatin1String( "akonadi_contactrc" ) );
146 KConfigGroup group( &config, QLatin1String( "CollectionDialog" ) );
147 const QSize size = group.readEntry( "Size", QSize(800, 500) );
148 if ( size.isValid() ) {
149 mParent->resize( size );
150 }
151 }
152
153 void writeConfig()
154 {
155 KConfig config( QLatin1String( "akonadi_contactrc" ) );
156 KConfigGroup group( &config, QLatin1String( "CollectionDialog" ) );
157 group.writeEntry( "Size", mParent->size() );
158 group.sync();
159 }
160
161 CollectionDialog *mParent;
162
163 ChangeRecorder *mMonitor;
164 CollectionFilterProxyModel *mMimeTypeFilterModel;
165 EntityRightsFilterModel *mRightsFilterModel;
166 EntityTreeView *mView;
167 AsyncSelectionHandler *mSelectionHandler;
168 QLabel *mTextLabel;
169 bool mAllowToCreateNewChildCollection;
170 bool mKeepTreeExpanded;
171 KRecursiveFilterProxyModel *mFilterCollection;
172 QCheckBox *mUseByDefault;
173
174 void slotSelectionChanged();
175 void slotAddChildCollection();
176 void slotCollectionCreationResult(KJob *job);
177 bool canCreateCollection(const Akonadi::Collection &parentCollection) const;
178 void changeCollectionDialogOptions(CollectionDialogOptions options);
179
180};
181
182void CollectionDialog::Private::slotSelectionChanged()
183{
184 mParent->enableButton(KDialog::Ok, mView->selectionModel()->selectedIndexes().count() > 0);
185 if (mAllowToCreateNewChildCollection) {
186 const Akonadi::Collection parentCollection = mParent->selectedCollection();
187 const bool canCreateChildCollections = canCreateCollection(parentCollection);
188
189 mParent->enableButton(KDialog::User1, (canCreateChildCollections && !parentCollection.isVirtual()));
190 if (parentCollection.isValid()) {
191 const bool canCreateItems = (parentCollection.rights() & Akonadi::Collection::CanCreateItem);
192 mParent->enableButton(KDialog::Ok, canCreateItems);
193 }
194 }
195}
196
197void CollectionDialog::Private::changeCollectionDialogOptions(CollectionDialogOptions options)
198{
199 mAllowToCreateNewChildCollection = (options & AllowToCreateNewChildCollection);
200 if (mAllowToCreateNewChildCollection) {
201 mParent->setButtons(Ok | Cancel | User1);
202 mParent->setButtonGuiItem(User1, KGuiItem(i18n("&New Subfolder..."), QLatin1String("folder-new"),
203 i18n("Create a new subfolder under the currently selected folder")));
204 mParent->enableButton(KDialog::User1, false);
205 connect(mParent, SIGNAL(user1Clicked()), mParent, SLOT(slotAddChildCollection()));
206 }
207 mKeepTreeExpanded = (options & KeepTreeExpanded);
208 if (mKeepTreeExpanded) {
209 mParent->connect(mRightsFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
210 mView, SLOT(expandAll()), Qt::UniqueConnection);
211 mView->expandAll();
212 }
213}
214
215bool CollectionDialog::Private::canCreateCollection(const Akonadi::Collection &parentCollection) const
216{
217 if (!parentCollection.isValid()) {
218 return false;
219 }
220
221 if ((parentCollection.rights() & Akonadi::Collection::CanCreateCollection)) {
222 const QStringList dialogMimeTypeFilter = mParent->mimeTypeFilter();
223 const QStringList parentCollectionMimeTypes = parentCollection.contentMimeTypes();
224 Q_FOREACH (const QString &mimetype, dialogMimeTypeFilter) {
225 if (parentCollectionMimeTypes.contains(mimetype)) {
226 return true;
227 }
228 }
229 return true;
230 }
231 return false;
232}
233
234void CollectionDialog::Private::slotAddChildCollection()
235{
236 const Akonadi::Collection parentCollection = mParent->selectedCollection();
237 if (canCreateCollection(parentCollection)) {
238 const QString name = KInputDialog::getText(i18nc("@title:window", "New Folder"),
239 i18nc("@label:textbox, name of a thing", "Name"),
240 QString(), 0, mParent);
241 if (name.isEmpty()) {
242 return;
243 }
244
245 Akonadi::Collection collection;
246 collection.setName(name);
247 collection.setParentCollection(parentCollection);
248 Akonadi::CollectionCreateJob *job = new Akonadi::CollectionCreateJob(collection);
249 connect(job, SIGNAL(result(KJob*)), mParent, SLOT(slotCollectionCreationResult(KJob*)));
250 }
251}
252
253void CollectionDialog::Private::slotCollectionCreationResult(KJob *job)
254{
255 if (job->error()) {
256 KMessageBox::error(mParent, i18n("Could not create folder: %1", job->errorString()),
257 i18n("Folder creation failed"));
258 }
259}
260
261CollectionDialog::CollectionDialog(QWidget *parent)
262 : KDialog(parent)
263 , d(new Private(0, this, CollectionDialog::None))
264{
265}
266
267CollectionDialog::CollectionDialog(QAbstractItemModel *model, QWidget *parent)
268 : KDialog(parent)
269 , d(new Private(model, this, CollectionDialog::None))
270{
271}
272
273CollectionDialog::CollectionDialog(CollectionDialogOptions options, QAbstractItemModel *model, QWidget *parent)
274 : KDialog(parent)
275 , d(new Private(model, this, options))
276{
277}
278
279CollectionDialog::~CollectionDialog()
280{
281 delete d;
282}
283
284Akonadi::Collection CollectionDialog::selectedCollection() const
285{
286 if (selectionMode() == QAbstractItemView::SingleSelection) {
287 const QModelIndex index = d->mView->currentIndex();
288 if (index.isValid()) {
289 return index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
290 }
291 }
292
293 return Collection();
294}
295
296Akonadi::Collection::List CollectionDialog::selectedCollections() const
297{
298 Collection::List collections;
299 const QItemSelectionModel *selectionModel = d->mView->selectionModel();
300 const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
301 foreach (const QModelIndex &index, selectedIndexes) {
302 if (index.isValid()) {
303 const Collection collection = index.model()->data(index, EntityTreeModel::CollectionRole).value<Collection>();
304 if (collection.isValid()) {
305 collections.append(collection);
306 }
307 }
308 }
309
310 return collections;
311}
312
313void CollectionDialog::setMimeTypeFilter(const QStringList &mimeTypes)
314{
315 if (mimeTypeFilter() == mimeTypes) {
316 return;
317 }
318
319 d->mMimeTypeFilterModel->clearFilters();
320 d->mMimeTypeFilterModel->addMimeTypeFilters(mimeTypes);
321
322 if (d->mMonitor) {
323 foreach (const QString &mimetype, mimeTypes) {
324 d->mMonitor->setMimeTypeMonitored(mimetype);
325 }
326 }
327}
328
329QStringList CollectionDialog::mimeTypeFilter() const
330{
331 return d->mMimeTypeFilterModel->mimeTypeFilters();
332}
333
334void CollectionDialog::setAccessRightsFilter(Collection::Rights rights)
335{
336 if (accessRightsFilter() == rights) {
337 return;
338 }
339 d->mRightsFilterModel->setAccessRights(rights);
340}
341
342Akonadi::Collection::Rights CollectionDialog::accessRightsFilter() const
343{
344 return d->mRightsFilterModel->accessRights();
345}
346
347void CollectionDialog::setDescription(const QString &text)
348{
349 d->mTextLabel->setText(text);
350 d->mTextLabel->show();
351}
352
353void CollectionDialog::setDefaultCollection(const Collection &collection)
354{
355 d->mSelectionHandler->waitForCollection(collection);
356}
357
358void CollectionDialog::setSelectionMode(QAbstractItemView::SelectionMode mode)
359{
360 d->mView->setSelectionMode(mode);
361}
362
363QAbstractItemView::SelectionMode CollectionDialog::selectionMode() const
364{
365 return d->mView->selectionMode();
366}
367
368void CollectionDialog::changeCollectionDialogOptions(CollectionDialogOptions options)
369{
370 d->changeCollectionDialogOptions(options);
371}
372
373void CollectionDialog::setUseFolderByDefault(bool b)
374{
375 d->mUseByDefault->setChecked(b);
376 d->mUseByDefault->show();
377}
378
379bool CollectionDialog::useFolderByDefault() const
380{
381 return d->mUseByDefault->isChecked();
382}
383
384#include "moc_collectiondialog.cpp"
385