Astyle kdelibs
[kdepim.git] / kaddressbook / mainwidget.cpp
blob986a843dfdfe4165d45b035216cd2b83dcb0b7df
1 /*
2 This file is part of KAddressBook.
4 Copyright (c) 2007 Tobias Koenig <tokoe@kde.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "mainwidget.h"
22 #include "contactswitcher.h"
23 #include "globalcontactmodel.h"
24 #include "modelcolumnmanager.h"
25 #include "printing/printingwizard.h"
26 #include "merge/manualmerge/mergecontactsdialog.h"
27 #include "merge/searchduplicate/searchandmergecontactduplicatecontactdialog.h"
28 #include "quicksearchwidget.h"
29 #include "settings.h"
30 #include "xxport/xxportmanager.h"
31 #include "utils.h"
32 #include "kaddressbookadaptor.h"
33 #include "categoryselectwidget.h"
34 #include "categoryfilterproxymodel.h"
35 #include "kaddressbook_options.h"
37 #include "sendmail/mailsenderjob.h"
38 #include "sendvcards/sendvcardsjob.h"
39 #include "gravatar/widgets/gravatarupdatedialog.h"
41 #include "kaddressbookgrantlee/formatter/grantleecontactformatter.h"
42 #include "kaddressbookgrantlee/formatter/grantleecontactgroupformatter.h"
43 #include "grantleetheme/grantleethememanager.h"
44 #include "grantleetheme/globalsettings_base.h"
46 #include "libkdepim/misc/uistatesaver.h"
48 #include <pimcommon/acl/collectionaclpage.h>
49 #include <pimcommon/acl/imapaclattribute.h>
51 #include <AkonadiWidgets/ETMViewStateSaver>
52 #include <AkonadiCore/CollectionFilterProxyModel>
53 #include <AkonadiCore/CollectionModel>
54 #include <AkonadiWidgets/ControlGui>
55 #include <AkonadiCore/EntityMimeTypeFilterModel>
56 #include <AkonadiWidgets/EntityTreeView>
57 #include <AkonadiWidgets/ItemView>
58 #include <AkonadiCore/MimeTypeChecker>
59 #include <AkonadiCore/AttributeFactory>
60 #include <AkonadiWidgets/CollectionPropertiesDialog>
61 #include <pimcommon/baloodebug/baloodebugdialog.h>
62 #include <KContacts/Addressee>
63 #include <QPointer>
64 #include "pimcommon/manageserversidesubscription/manageserversidesubscriptionjob.h"
65 #include "pimcommon/util/pimutil.h"
67 #include <Akonadi/Contact/ContactDefaultActions>
68 #include <Akonadi/Contact/ContactGroupEditorDialog>
69 #include <Akonadi/Contact/ContactGroupViewer>
70 #include <Akonadi/Contact/ContactsFilterProxyModel>
71 #include <Akonadi/Contact/ContactsTreeModel>
72 #include <Akonadi/Contact/ContactViewer>
73 #include <Akonadi/Contact/StandardContactActionManager>
75 #include <KContacts/ContactGroup>
76 #include "kaddressbook_debug.h"
77 #include <QAction>
78 #include <QApplication>
79 #include <KActionCollection>
80 #include <KActionMenu>
81 #include <KCheckableProxyModel>
82 #include <KDescendantsProxyModel>
83 #include <KLocalizedString>
84 #include <KSelectionProxyModel>
85 #include <QTextBrowser>
86 #include <KToggleAction>
87 #include <KCMultiDialog>
88 #include <kdeprintdialog.h>
89 #include <KPrintPreview>
90 #include <KXMLGUIClient>
91 #include <KIconLoader>
92 #include <KMessageBox>
94 #include <QActionGroup>
95 #include <QHBoxLayout>
96 #include <QHeaderView>
97 #include <QPrinter>
98 #include <QPrintDialog>
99 #include <QSplitter>
100 #include <QStackedWidget>
101 #include <QDBusConnection>
102 #include <QDesktopServices>
104 namespace
106 static bool isStructuralCollection(const Akonadi::Collection &collection)
108 QStringList mimeTypes;
109 mimeTypes << KContacts::Addressee::mimeType() << KContacts::ContactGroup::mimeType();
110 const QStringList collectionMimeTypes = collection.contentMimeTypes();
111 foreach (const QString &mimeType, mimeTypes) {
112 if (collectionMimeTypes.contains(mimeType)) {
113 return false;
116 return true;
119 class StructuralCollectionsNotCheckableProxy : public KCheckableProxyModel
121 public:
122 explicit StructuralCollectionsNotCheckableProxy(QObject *parent)
123 : KCheckableProxyModel(parent)
127 QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE
129 if (!index.isValid()) {
130 return QVariant();
133 if (role == Qt::CheckStateRole) {
134 // Don't show the checkbox if the collection can't contain incidences
135 const Akonadi::Collection collection =
136 index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
137 if (collection.isValid() && isStructuralCollection(collection)) {
138 return QVariant();
141 return KCheckableProxyModel::data(index, role);
147 MainWidget::MainWidget(KXMLGUIClient *guiClient, QWidget *parent)
148 : QWidget(parent), mAllContactsModel(Q_NULLPTR), mXmlGuiClient(guiClient), mGrantleeThemeManager(Q_NULLPTR), mQuickSearchAction(Q_NULLPTR)
151 (void) new KaddressbookAdaptor(this);
152 QDBusConnection::sessionBus().registerObject(QStringLiteral("/KAddressBook"), this);
154 mXXPortManager = new XXPortManager(this);
155 Akonadi::AttributeFactory::registerAttribute<PimCommon::ImapAclAttribute>();
157 setupGui();
158 setupActions(guiClient->actionCollection());
161 * The item models, proxies and views have the following structure:
163 * mItemView
166 * mContactsFilterModel
169 * mCategorySelectWidget --> mCategoryFilterModel
172 * mItemTree
175 * | mAllContactsModel
176 * | ^
177 * | |
178 * mCollectionView selectionProxyModel descendantsModel
179 * ^ ^ ^ ^
180 * | | | |
181 * | selectionModel | |
182 * | | | |
183 * proxyModel ---------' | |
184 * ^ | |
185 * | | |
186 * mCollectionTree | |
187 * ^ | |
188 * | | _______________/
189 * \ / /
190 * GlobalContactModel::instance()
193 * GlobalContactModel::instance(): The global contact model (contains collections and items)
194 * mCollectionTree: Filters out all items
195 * proxyModel: Allows the user to select collections by checkboxes
196 * selectionModel: Represents the selected collections that have been
197 * selected in proxyModel
198 * mCollectionView: Shows the collections (address books) in a view
199 * selectionProxyModel: Filters out all collections and items that are no children
200 * of the collections currently selected in selectionModel
201 * mItemTree: Filters out all collections
202 * mCategorySelectWidget: Selects a list of categories for filtering
203 * mCategoryFilterModel: Filters the contacts by the selected categories
204 * mContactsFilterModel: Filters the contacts by the content of mQuickSearchWidget
205 * mItemView: Shows the items (contacts and contact groups) in a view
207 * descendantsModel: Flattens the item/collection tree to a list
208 * mAllContactsModel: Provides a list of all available contacts from all
209 * address books
212 mCollectionTree = new Akonadi::EntityMimeTypeFilterModel(this);
213 mCollectionTree->setDynamicSortFilter(true);
214 mCollectionTree->setSortCaseSensitivity(Qt::CaseInsensitive);
215 mCollectionTree->setSourceModel(GlobalContactModel::instance()->model());
216 mCollectionTree->addMimeTypeInclusionFilter(Akonadi::Collection::mimeType());
217 mCollectionTree->setHeaderGroup(Akonadi::EntityTreeModel::CollectionTreeHeaders);
219 mCollectionSelectionModel = new QItemSelectionModel(mCollectionTree);
220 StructuralCollectionsNotCheckableProxy *checkableProxyModel =
221 new StructuralCollectionsNotCheckableProxy(this);
222 checkableProxyModel->setSelectionModel(mCollectionSelectionModel);
223 checkableProxyModel->setSourceModel(mCollectionTree);
225 mCollectionView->setModel(checkableProxyModel);
226 mCollectionView->setXmlGuiClient(guiClient);
227 mCollectionView->header()->setDefaultAlignment(Qt::AlignCenter);
228 mCollectionView->header()->setSortIndicatorShown(false);
230 connect(mCollectionView->model(), &QAbstractItemModel::rowsInserted,
231 this, &MainWidget::slotCheckNewCalendar);
233 connect(mCollectionView, SIGNAL(currentChanged(Akonadi::Collection)),
234 this, SLOT(slotCurrentCollectionChanged(Akonadi::Collection)));
236 KSelectionProxyModel *selectionProxyModel =
237 new KSelectionProxyModel(mCollectionSelectionModel, this);
238 selectionProxyModel->setSourceModel(GlobalContactModel::instance()->model());
239 selectionProxyModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection);
241 mItemTree = new Akonadi::EntityMimeTypeFilterModel(this);
242 mItemTree->setSourceModel(selectionProxyModel);
243 mItemTree->addMimeTypeExclusionFilter(Akonadi::Collection::mimeType());
244 mItemTree->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders);
246 mCategoryFilterModel = new CategoryFilterProxyModel(this);
247 mCategoryFilterModel->setSourceModel(mItemTree);
248 mCategoryFilterModel->setFilterCategories(mCategorySelectWidget->filterTags());
249 mCategoryFilterModel->setFilterEnabled(true);
251 connect(mCategorySelectWidget, &CategorySelectWidget::filterChanged,
252 mCategoryFilterModel, &CategoryFilterProxyModel::setFilterCategories);
254 mContactsFilterModel = new Akonadi::ContactsFilterProxyModel(this);
255 mContactsFilterModel->setSourceModel(mCategoryFilterModel);
257 connect(mQuickSearchWidget, &QuickSearchWidget::filterStringChanged,
258 mContactsFilterModel, &Akonadi::ContactsFilterProxyModel::setFilterString);
259 connect(mQuickSearchWidget, &QuickSearchWidget::filterStringChanged,
260 this, &MainWidget::selectFirstItem);
261 connect(mQuickSearchWidget, SIGNAL(arrowDownKeyPressed()),
262 mItemView, SLOT(setFocus()));
264 mItemView->setModel(mContactsFilterModel);
265 mItemView->setXmlGuiClient(guiClient);
266 mItemView->setSelectionMode(QAbstractItemView::ExtendedSelection);
267 mItemView->setRootIsDecorated(false);
268 mItemView->header()->setDefaultAlignment(Qt::AlignCenter);
270 mXXPortManager->setSelectionModel(mItemView->selectionModel());
272 mActionManager = new Akonadi::StandardContactActionManager(guiClient->actionCollection(), this);
273 mActionManager->setCollectionSelectionModel(mCollectionView->selectionModel());
274 mActionManager->setItemSelectionModel(mItemView->selectionModel());
276 QList<Akonadi::StandardActionManager::Type> standardActions;
277 standardActions << Akonadi::StandardActionManager::CreateCollection
278 << Akonadi::StandardActionManager::DeleteCollections
279 << Akonadi::StandardActionManager::SynchronizeCollections
280 << Akonadi::StandardActionManager::CollectionProperties
281 << Akonadi::StandardActionManager::CopyItems
282 << Akonadi::StandardActionManager::Paste
283 << Akonadi::StandardActionManager::DeleteItems
284 << Akonadi::StandardActionManager::CutItems
285 << Akonadi::StandardActionManager::CreateResource
286 << Akonadi::StandardActionManager::DeleteResources
287 << Akonadi::StandardActionManager::ResourceProperties
288 << Akonadi::StandardActionManager::SynchronizeResources
289 << Akonadi::StandardActionManager::SynchronizeCollectionsRecursive
290 << Akonadi::StandardActionManager::MoveItemToMenu
291 << Akonadi::StandardActionManager::CopyItemToMenu;
293 Q_FOREACH (Akonadi::StandardActionManager::Type standardAction, standardActions) {
294 mActionManager->createAction(standardAction);
297 QList<Akonadi::StandardContactActionManager::Type> contactActions;
298 contactActions << Akonadi::StandardContactActionManager::CreateContact
299 << Akonadi::StandardContactActionManager::CreateContactGroup
300 << Akonadi::StandardContactActionManager::EditItem;
302 Q_FOREACH (Akonadi::StandardContactActionManager::Type contactAction, contactActions) {
303 mActionManager->createAction(contactAction);
305 static bool pageRegistered = false;
307 if (!pageRegistered) {
308 Akonadi::CollectionPropertiesDialog::registerPage(new PimCommon::CollectionAclPageFactory);
309 pageRegistered = true;
312 const QStringList pages =
313 QStringList() << QStringLiteral("Akonadi::CollectionGeneralPropertiesPage")
314 << QStringLiteral("Akonadi::CachePolicyPage")
315 << QStringLiteral("PimCommon::CollectionAclPage");
317 mActionManager->setCollectionPropertiesPageNames(pages);
319 connect(mItemView, SIGNAL(currentChanged(Akonadi::Item)),
320 this, SLOT(itemSelected(Akonadi::Item)));
321 connect(mItemView, SIGNAL(doubleClicked(Akonadi::Item)),
322 mActionManager->action(Akonadi::StandardContactActionManager::EditItem),
323 SLOT(trigger()));
324 connect(mItemView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
325 this, SLOT(itemSelectionChanged(QModelIndex,QModelIndex)));
327 // show the contact details view as default
328 mDetailsViewStack->setCurrentWidget(mContactDetails);
330 mContactSwitcher->setView(mItemView);
332 Akonadi::ControlGui::widgetNeedsAkonadi(this);
334 mModelColumnManager = new ModelColumnManager(GlobalContactModel::instance()->model(), this);
335 mModelColumnManager->setWidget(mItemView->header());
336 mModelColumnManager->load();
338 QMetaObject::invokeMethod(this, "delayedInit", Qt::QueuedConnection);
339 updateQuickSearchText();
342 void MainWidget::configure()
344 KCMultiDialog dlg(this);
345 dlg.addModule(QStringLiteral("akonadicontact_actions.desktop"));
346 dlg.addModule(QStringLiteral("kcmldap.desktop"));
348 dlg.exec();
351 void MainWidget::handleCommandLine(const QStringList &arguments)
353 QCommandLineParser parser;
354 kaddressbook_options(&parser);
355 parser.process(arguments);
357 if (parser.isSet(QStringLiteral("import"))) {
358 for (const QString &url : parser.positionalArguments()) {
359 importManager()->importFile(QUrl::fromUserInput(url));
364 XXPortManager *MainWidget::importManager() const
366 return mXXPortManager;
369 void MainWidget::updateQuickSearchText()
371 mQuickSearchWidget->updateQuickSearchText(i18nc("@label Search contacts in list", "Search...<%1>", mQuickSearchAction->shortcut().toString()));
374 void MainWidget::delayedInit()
376 setViewMode(0); // get default from settings
378 const KConfigGroup group(Settings::self()->config(), "UiState_ContactView");
379 KPIM::UiStateSaver::restoreState(mItemView, group);
381 #if defined(HAVE_PRISON)
382 mXmlGuiClient->
383 actionCollection()->
384 action(QStringLiteral("options_show_qrcodes"))->setChecked(showQRCodes());
385 #endif
387 connect(GlobalContactModel::instance()->model(), SIGNAL(modelAboutToBeReset()),
388 SLOT(saveState()));
389 connect(GlobalContactModel::instance()->model(), SIGNAL(modelReset()),
390 SLOT(restoreState()));
391 connect(qApp, &QApplication::aboutToQuit, this, &MainWidget::saveState);
393 restoreState();
394 updateQuickSearchText();
397 MainWidget::~MainWidget()
399 mModelColumnManager->store();
400 saveSplitterStates();
402 KConfigGroup group(Settings::self()->config(), "UiState_ContactView");
403 KPIM::UiStateSaver::saveState(mItemView, group);
405 saveState();
406 delete mGrantleeThemeManager;
408 Settings::self()->save();
411 void MainWidget::restoreState()
413 // collection view
415 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
416 saver->setView(mCollectionView);
418 const KConfigGroup group(Settings::self()->config(), "CollectionViewState");
419 saver->restoreState(group);
422 // collection view
424 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
425 saver->setSelectionModel(mCollectionSelectionModel);
427 const KConfigGroup group(Settings::self()->config(), "CollectionViewCheckState");
428 saver->restoreState(group);
431 // item view
433 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
434 saver->setView(mItemView);
435 saver->setSelectionModel(mItemView->selectionModel());
437 const KConfigGroup group(Settings::self()->config(), "ItemViewState");
438 saver->restoreState(group);
442 void MainWidget::saveState()
444 // collection view
446 Akonadi::ETMViewStateSaver saver;
447 saver.setView(mCollectionView);
449 KConfigGroup group(Settings::self()->config(), "CollectionViewState");
450 saver.saveState(group);
451 group.sync();
454 // collection view
456 Akonadi::ETMViewStateSaver saver;
457 saver.setSelectionModel(mCollectionSelectionModel);
459 KConfigGroup group(Settings::self()->config(), "CollectionViewCheckState");
460 saver.saveState(group);
461 group.sync();
464 // item view
466 Akonadi::ETMViewStateSaver saver;
467 saver.setView(mItemView);
468 saver.setSelectionModel(mItemView->selectionModel());
470 KConfigGroup group(Settings::self()->config(), "ItemViewState");
471 saver.saveState(group);
472 group.sync();
476 void MainWidget::setupGui()
478 // the horizontal main layout
479 QHBoxLayout *layout = new QHBoxLayout(this);
480 layout->setMargin(0);
482 // Splitter 1 contains the two main parts of the GUI:
483 // - collection and item view splitter 2 on the left (see below)
484 // - details pane on the right, that contains
485 // - details view stack on the top
486 // - contact switcher at the bottom
487 mMainWidgetSplitter1 = new QSplitter(Qt::Horizontal);
488 mMainWidgetSplitter1->setObjectName(QStringLiteral("MainWidgetSplitter1"));
489 layout->addWidget(mMainWidgetSplitter1);
491 // Splitter 2 contains the remaining parts of the GUI:
492 // - collection view on either the left or the top
493 // - item view on either the right or the bottom
494 // The orientation of this splitter is changed for either
495 // a three or two column view; in simple mode it is hidden.
496 mMainWidgetSplitter2 = new QSplitter(Qt::Vertical);
497 mMainWidgetSplitter2->setObjectName(QStringLiteral("MainWidgetSplitter2"));
498 mMainWidgetSplitter1->addWidget(mMainWidgetSplitter2);
500 // the collection view
501 mCollectionView = new Akonadi::EntityTreeView();
502 mMainWidgetSplitter2->addWidget(mCollectionView);
504 // the items view
505 mItemView = new Akonadi::EntityTreeView();
506 mItemView->setObjectName(QStringLiteral("ContactView"));
507 mItemView->setDefaultPopupMenu(QStringLiteral("akonadi_itemview_contextmenu"));
508 mItemView->setAlternatingRowColors(true);
509 mMainWidgetSplitter2->addWidget(mItemView);
511 // the details pane that contains the details view stack and contact switcher
512 mDetailsPane = new QWidget;
513 mMainWidgetSplitter1->addWidget(mDetailsPane);
515 mMainWidgetSplitter1->setStretchFactor(1, 9); // maximum width for detail
516 mMainWidgetSplitter2->setStretchFactor(1, 9); // for intuitive resizing
517 mMainWidgetSplitter2->setChildrenCollapsible(false);
518 mMainWidgetSplitter1->setChildrenCollapsible(false);
520 QVBoxLayout *detailsPaneLayout = new QVBoxLayout(mDetailsPane);
521 detailsPaneLayout->setMargin(0);
523 // the details view stack
524 mDetailsViewStack = new QStackedWidget();
525 detailsPaneLayout->addWidget(mDetailsViewStack);
527 // the details widget for contacts
528 mContactDetails = new Akonadi::ContactViewer(mDetailsViewStack);
529 mDetailsViewStack->addWidget(mContactDetails);
531 // the details widget for contact groups
532 mContactGroupDetails = new Akonadi::ContactGroupViewer(mDetailsViewStack);
533 mDetailsViewStack->addWidget(mContactGroupDetails);
535 // the details widget for empty items
536 mEmptyDetails = new QTextBrowser(mDetailsViewStack);
537 mDetailsViewStack->addWidget(mEmptyDetails);
539 // the contact switcher for the simple gui mode
540 mContactSwitcher = new ContactSwitcher;
541 detailsPaneLayout->addWidget(mContactSwitcher);
542 mContactSwitcher->setVisible(false);
544 // the quick search widget which is embedded in the toolbar action
545 mQuickSearchWidget = new QuickSearchWidget;
546 mQuickSearchWidget->setMaximumWidth(500);
548 // the category filter widget which is embedded in the toolbar action
549 mCategorySelectWidget = new CategorySelectWidget;
551 // setup the default actions
552 Akonadi::ContactDefaultActions *actions = new Akonadi::ContactDefaultActions(this);
553 actions->connectToView(mContactDetails);
554 actions->connectToView(mContactGroupDetails);
555 mFormatter = new KAddressBookGrantlee::GrantleeContactFormatter;
557 mContactDetails->setContactFormatter(mFormatter);
559 mGroupFormatter = new KAddressBookGrantlee::GrantleeContactGroupFormatter;
561 mContactGroupDetails->setContactGroupFormatter(mGroupFormatter);
564 void MainWidget::setupActions(KActionCollection *collection)
566 mGrantleeThemeManager = new GrantleeTheme::GrantleeThemeManager(GrantleeTheme::GrantleeThemeManager::Addressbook, QStringLiteral("theme.desktop"), collection, QStringLiteral("kaddressbook/viewertemplates/"));
567 mGrantleeThemeManager->setDownloadNewStuffConfigFile(QStringLiteral("kaddressbook_themes.knsrc"));
568 connect(mGrantleeThemeManager, &GrantleeTheme::GrantleeThemeManager::grantleeThemeSelected, this, &MainWidget::slotGrantleeThemeSelected);
569 connect(mGrantleeThemeManager, &GrantleeTheme::GrantleeThemeManager::updateThemes, this, &MainWidget::slotGrantleeThemesUpdated);
571 KActionMenu *themeMenu = new KActionMenu(i18n("&Themes"), this);
572 collection->addAction(QStringLiteral("theme_menu"), themeMenu);
574 initGrantleeThemeName();
575 QActionGroup *group = new QActionGroup(this);
576 mGrantleeThemeManager->setThemeMenu(themeMenu);
577 mGrantleeThemeManager->setActionGroup(group);
579 QAction *action = KStandardAction::print(this, SLOT(print()), collection);
580 action->setWhatsThis(
581 i18nc("@info:whatsthis",
582 "Print the complete address book or a selected number of contacts."));
584 if (KPrintPreview::isAvailable()) {
585 KStandardAction::printPreview(this, SLOT(printPreview()), collection);
588 QWidgetAction *quicksearch = new QWidgetAction(this);
589 quicksearch->setText(i18n("Quick search"));
590 quicksearch->setDefaultWidget(mQuickSearchWidget);
591 collection->addAction(QStringLiteral("quick_search"), quicksearch);
593 QWidgetAction *categoryFilter = new QWidgetAction(this);
594 categoryFilter->setText(i18n("Category filter"));
595 categoryFilter->setDefaultWidget(mCategorySelectWidget);
596 collection->addAction(QStringLiteral("category_filter"), categoryFilter);
598 action = collection->addAction(QStringLiteral("select_all"));
599 action->setText(i18n("Select All"));
600 collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_A));
601 action->setWhatsThis(i18n("Select all contacts in the current address book view."));
602 connect(action, &QAction::triggered, mItemView, &Akonadi::EntityTreeView::selectAll);
604 #if defined(HAVE_PRISON)
605 KToggleAction *qrtoggleAction;
606 qrtoggleAction = collection->add<KToggleAction>(QStringLiteral("options_show_qrcodes"));
607 qrtoggleAction->setText(i18n("Show QR Codes"));
608 qrtoggleAction->setWhatsThis(i18n("Show QR Codes in the contact."));
609 connect(qrtoggleAction, &KToggleAction::toggled, this, &MainWidget::setQRCodeShow);
610 #endif
612 mViewModeGroup = new QActionGroup(this);
614 QAction *act = new QAction(i18nc("@action:inmenu", "Simple (one column)"), mViewModeGroup);
615 act->setCheckable(true);
616 act->setData(1);
617 collection->setDefaultShortcut(act, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1));
618 act->setWhatsThis(i18n("Show a simple mode of the address book view."));
619 collection->addAction(QStringLiteral("view_mode_simple"), act);
621 act = new QAction(i18nc("@action:inmenu", "Two Columns"), mViewModeGroup);
622 act->setCheckable(true);
623 act->setData(2);
624 collection->addAction(QStringLiteral("view_mode_2columns"), act);
625 collection->setDefaultShortcut(act, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2));
627 act = new QAction(i18nc("@action:inmenu", "Three Columns"), mViewModeGroup);
628 act->setCheckable(true);
629 act->setData(3);
630 collection->setDefaultShortcut(act, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3));
631 collection->addAction(QStringLiteral("view_mode_3columns"), act);
633 connect(mViewModeGroup, SIGNAL(triggered(QAction*)), SLOT(setViewMode(QAction*)));
635 // import actions
636 action = collection->addAction(QStringLiteral("file_import_vcard"));
637 action->setText(i18n("Import vCard..."));
638 action->setWhatsThis(i18n("Import contacts from a vCard file."));
639 mXXPortManager->addImportAction(action, QStringLiteral("vcard30"));
641 action = collection->addAction(QStringLiteral("file_import_csv"));
642 action->setText(i18n("Import CSV file..."));
643 action->setWhatsThis(i18n("Import contacts from a file in comma separated value format."));
644 mXXPortManager->addImportAction(action, QStringLiteral("csv"));
646 action = collection->addAction(QStringLiteral("file_import_ldif"));
647 action->setText(i18n("Import LDIF file..."));
648 action->setWhatsThis(i18n("Import contacts from an LDIF file."));
649 mXXPortManager->addImportAction(action, QStringLiteral("ldif"));
651 action = collection->addAction(QStringLiteral("file_import_ldap"));
652 action->setText(i18n("Import From LDAP server..."));
653 action->setWhatsThis(i18n("Import contacts from an LDAP server."));
654 mXXPortManager->addImportAction(action, QStringLiteral("ldap"));
656 action = collection->addAction(QStringLiteral("file_import_gmx"));
657 action->setText(i18n("Import GMX file..."));
658 action->setWhatsThis(i18n("Import contacts from a GMX address book file."));
659 mXXPortManager->addImportAction(action, QStringLiteral("gmx"));
661 // export actions
662 action = collection->addAction(QStringLiteral("file_export_vcard40"));
663 action->setText(i18n("Export vCard 4.0..."));
664 action->setWhatsThis(i18n("Export contacts to a vCard 4.0 file."));
665 mXXPortManager->addExportAction(action, QStringLiteral("vcard40"));
667 action = collection->addAction(QStringLiteral("file_export_vcard30"));
668 action->setText(i18n("Export vCard 3.0..."));
669 action->setWhatsThis(i18n("Export contacts to a vCard 3.0 file."));
670 mXXPortManager->addExportAction(action, QStringLiteral("vcard30"));
672 action = collection->addAction(QStringLiteral("file_export_vcard21"));
673 action->setText(i18n("Export vCard 2.1..."));
674 action->setWhatsThis(i18n("Export contacts to a vCard 2.1 file."));
675 mXXPortManager->addExportAction(action, QStringLiteral("vcard21"));
677 action = collection->addAction(QStringLiteral("file_export_csv"));
678 action->setText(i18n("Export CSV file..."));
679 action->setWhatsThis(i18n("Export contacts to a file in comma separated value format."));
680 mXXPortManager->addExportAction(action, QStringLiteral("csv"));
682 action = collection->addAction(QStringLiteral("file_export_ldif"));
683 action->setText(i18n("Export LDIF file..."));
684 action->setWhatsThis(i18n("Export contacts to an LDIF file."));
685 mXXPortManager->addExportAction(action, QStringLiteral("ldif"));
687 action = collection->addAction(QStringLiteral("file_export_gmx"));
688 action->setText(i18n("Export GMX file..."));
689 action->setWhatsThis(i18n("Export contacts to a GMX address book file."));
690 mXXPortManager->addExportAction(action, QStringLiteral("gmx"));
692 KToggleAction *actTheme = mGrantleeThemeManager->actionForTheme();
693 if (actTheme) {
694 actTheme->setChecked(true);
697 action = collection->addAction(QStringLiteral("merge_contacts"));
698 action->setText(i18n("Merge Contacts..."));
699 connect(action, &QAction::triggered, this, &MainWidget::mergeContacts);
701 action = collection->addAction(QStringLiteral("search_duplicate_contacts"));
702 action->setText(i18n("Search Duplicate Contacts..."));
703 connect(action, &QAction::triggered, this, &MainWidget::slotSearchDuplicateContacts);
705 mQuickSearchAction = new QAction(i18n("Set Focus to Quick Search"), this);
706 //If change shortcut change in quicksearchwidget->lineedit->setPlaceholderText
707 collection->addAction(QStringLiteral("focus_to_quickseach"), mQuickSearchAction);
708 connect(mQuickSearchAction, &QAction::triggered, mQuickSearchWidget, &QuickSearchWidget::slotFocusQuickSearch);
709 collection->setDefaultShortcut(mQuickSearchAction, QKeySequence(Qt::ALT + Qt::Key_Q));
711 action = collection->addAction(QStringLiteral("send_mail"));
712 action->setText(i18n("Send an email..."));
713 action->setIcon(KIconLoader::global()->loadIcon(QStringLiteral("mail-message-new"), KIconLoader::Small));
714 connect(action, &QAction::triggered, this, &MainWidget::slotSendMail);
716 action = collection->addAction(QStringLiteral("send_vcards"));
717 action->setText(i18n("Send vCards..."));
718 action->setIcon(KIconLoader::global()->loadIcon(QStringLiteral("mail-message-new"), KIconLoader::Small));
719 connect(action, &QAction::triggered, this, &MainWidget::slotSendVcards);
721 if (!qgetenv("KDEPIM_BALOO_DEBUG").isEmpty()) {
722 action = collection->addAction(QStringLiteral("debug_baloo"));
723 //Don't translate it. It's just for debug
724 action->setText(QStringLiteral("Debug baloo..."));
725 connect(action, SIGNAL(triggered(bool)), this, SLOT(slotDebugBaloo()));
728 mServerSideSubscription = new QAction(QIcon::fromTheme(QStringLiteral("folder-bookmarks")), i18n("Serverside Subscription..."), this);
729 collection->addAction(QStringLiteral("serverside_subscription"), mServerSideSubscription);
730 connect(mServerSideSubscription, &QAction::triggered, this, &MainWidget::slotServerSideSubscription);
732 action = collection->addAction(QStringLiteral("gravatar"));
733 action->setText(i18n("Check gravatar..."));
734 connect(action, &QAction::triggered, this, &MainWidget::slotCheckGravatar);
737 void MainWidget::printPreview()
739 QPrinter printer;
740 printer.setDocName(i18n("Address Book"));
741 printer.setOutputFileName(Settings::self()->defaultFileName());
742 printer.setOutputFormat(QPrinter::PdfFormat);
743 printer.setCollateCopies(true);
745 KPrintPreview previewdlg(&printer, this);
746 KABPrinting::PrintingWizard wizard(&printer, mItemView->selectionModel(), this);
747 wizard.setDefaultAddressBook(currentAddressBook());
749 const int result = wizard.exec();
750 if (result) {
751 Settings::self()->setDefaultFileName(printer.outputFileName());
752 Settings::self()->setPrintingStyle(wizard.printingStyle());
753 Settings::self()->setSortOrder(wizard.sortOrder());
754 previewdlg.exec();
758 void MainWidget::print()
760 QPrinter printer;
761 printer.setDocName(i18n("Address Book"));
762 printer.setOutputFileName(Settings::self()->defaultFileName());
763 printer.setCollateCopies(true);
765 QPointer<QPrintDialog> printDialog = KdePrint::createPrintDialog(&printer, this);
767 printDialog->setWindowTitle(i18n("Print Contacts"));
768 if (!printDialog->exec() || !printDialog) {
769 delete printDialog;
770 return;
772 KABPrinting::PrintingWizard wizard(&printer, mItemView->selectionModel(), this);
773 wizard.setDefaultAddressBook(currentAddressBook());
775 wizard.exec(); //krazy:exclude=crashy
777 Settings::self()->setDefaultFileName(printer.outputFileName());
778 Settings::self()->setPrintingStyle(wizard.printingStyle());
779 Settings::self()->setSortOrder(wizard.sortOrder());
782 void MainWidget::newContact()
784 mActionManager->action(Akonadi::StandardContactActionManager::CreateContact)->trigger();
787 void MainWidget::newGroup()
789 mActionManager->action(Akonadi::StandardContactActionManager::CreateContactGroup)->trigger();
793 * Depending on the mime type of the selected item, this method
794 * brings up the right view on the detail view stack and sets the
795 * selected item on it.
797 void MainWidget::itemSelected(const Akonadi::Item &item)
799 if (Akonadi::MimeTypeChecker::isWantedItem(item, KContacts::Addressee::mimeType())) {
800 mDetailsViewStack->setCurrentWidget(mContactDetails);
801 mContactDetails->setContact(item);
802 } else if (Akonadi::MimeTypeChecker::isWantedItem(item, KContacts::ContactGroup::mimeType())) {
803 mDetailsViewStack->setCurrentWidget(mContactGroupDetails);
804 mContactGroupDetails->setContactGroup(item);
809 * Catch when the selection has gone ( e.g. an empty address book has been selected )
810 * clear the details view in this case.
812 void MainWidget::itemSelectionChanged(const QModelIndex &current, const QModelIndex &)
814 if (!current.isValid()) {
815 mDetailsViewStack->setCurrentWidget(mEmptyDetails);
819 void MainWidget::selectFirstItem()
821 // Whenever the quick search has changed, we select the first item
822 // in the item view, so that the detailsview is updated
823 if (mItemView && mItemView->selectionModel()) {
824 mItemView->selectionModel()->setCurrentIndex(mItemView->model()->index(0, 0),
825 QItemSelectionModel::Rows |
826 QItemSelectionModel::ClearAndSelect);
830 bool MainWidget::showQRCodes()
832 #if defined(HAVE_PRISON)
833 KConfig config(QStringLiteral("akonadi_contactrc"));
834 KConfigGroup group(&config, QStringLiteral("View"));
835 return group.readEntry("QRCodes", true);
836 #else
837 return true;
838 #endif
841 void MainWidget::setQRCodeShow(bool on)
843 #if defined(HAVE_PRISON)
844 // must write the configuration setting first before updating the view.
845 KConfig config(QStringLiteral("akonadi_contactrc"));
846 KConfigGroup group(&config, QStringLiteral("View"));
847 group.writeEntry("QRCodes", on);
848 if (mItemView->model()) {
849 mItemView->setCurrentIndex(mItemView->model()->index(0, 0));
851 #else
852 Q_UNUSED(on);
853 #endif
856 Akonadi::Collection MainWidget::currentAddressBook() const
858 if (mCollectionView->selectionModel() && mCollectionView->selectionModel()->hasSelection()) {
859 const QModelIndex index = mCollectionView->selectionModel()->selectedIndexes().first();
860 const Akonadi::Collection collection =
861 index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
863 return collection;
866 return Akonadi::Collection();
869 QAbstractItemModel *MainWidget::allContactsModel()
871 if (!mAllContactsModel) {
872 KDescendantsProxyModel *descendantsModel = new KDescendantsProxyModel(this);
873 descendantsModel->setSourceModel(GlobalContactModel::instance()->model());
875 mAllContactsModel = new Akonadi::EntityMimeTypeFilterModel(this);
876 mAllContactsModel->setSourceModel(descendantsModel);
877 mAllContactsModel->addMimeTypeExclusionFilter(Akonadi::Collection::mimeType());
878 mAllContactsModel->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders);
881 return mAllContactsModel;
884 void MainWidget::setViewMode(QAction *action)
886 setViewMode(action->data().toInt());
889 void MainWidget::setViewMode(int mode)
891 int currentMode = Settings::self()->viewMode();
892 //qCDebug(KADDRESSBOOK_LOG) << "cur" << currentMode << "new" << mode;
893 if (mode == currentMode) {
894 return; // nothing to do
897 if (mode == 0) {
898 mode = currentMode;// initialisation, no save
899 } else {
900 saveSplitterStates(); // for 2- or 3-column mode
902 if (mode == 1) { // simple mode
903 mMainWidgetSplitter2->setVisible(false);
904 mDetailsPane->setVisible(true);
905 mContactSwitcher->setVisible(true);
906 } else {
907 mMainWidgetSplitter2->setVisible(true);
908 mContactSwitcher->setVisible(false);
910 if (mode == 2) { // 2 columns
911 mMainWidgetSplitter2->setOrientation(Qt::Vertical);
912 } else if (mode == 3) { // 3 columns
913 mMainWidgetSplitter2->setOrientation(Qt::Horizontal);
917 Settings::self()->setViewMode(mode); // save new mode in settings
918 restoreSplitterStates(); // restore state for new mode
919 mViewModeGroup->actions().at(mode - 1)->setChecked(true);
921 if (mItemView->model()) {
922 mItemView->setCurrentIndex(mItemView->model()->index(0, 0));
926 void MainWidget::saveSplitterStates() const
928 // The splitter states are saved separately for each column view mode,
929 // but only if not in simple mode (1 column).
930 int currentMode = Settings::self()->viewMode();
931 if (currentMode == 1) {
932 return;
935 const QString groupName = QStringLiteral("UiState_MainWidgetSplitter_%1").arg(currentMode);
936 //qCDebug(KADDRESSBOOK_LOG) << "saving to group" << groupName;
937 KConfigGroup group(Settings::self()->config(), groupName);
938 KPIM::UiStateSaver::saveState(mMainWidgetSplitter1, group);
939 KPIM::UiStateSaver::saveState(mMainWidgetSplitter2, group);
942 void MainWidget::restoreSplitterStates()
944 // The splitter states are restored as appropriate for the current
945 // column view mode, but not for simple mode (1 column).
946 int currentMode = Settings::self()->viewMode();
947 if (currentMode == 1) {
948 return;
951 const QString groupName = QStringLiteral("UiState_MainWidgetSplitter_%1").arg(currentMode);
952 //qCDebug(KADDRESSBOOK_LOG) << "restoring from group" << groupName;
953 KConfigGroup group(Settings::self()->config(), groupName);
954 KPIM::UiStateSaver::restoreState(mMainWidgetSplitter1, group);
955 KPIM::UiStateSaver::restoreState(mMainWidgetSplitter2, group);
958 void MainWidget::initGrantleeThemeName()
960 QString themeName = GrantleeTheme::GrantleeSettings::self()->grantleeAddressBookThemeName();
961 if (themeName.isEmpty()) {
962 themeName = QStringLiteral("default");
964 mFormatter->setGrantleeTheme(mGrantleeThemeManager->theme(themeName));
965 mGroupFormatter->setGrantleeTheme(mGrantleeThemeManager->theme(themeName));
968 void MainWidget::slotGrantleeThemeSelected()
970 initGrantleeThemeName();
971 if (mItemView->model()) {
972 mItemView->setCurrentIndex(mItemView->model()->index(0, 0));
976 void MainWidget::slotGrantleeThemesUpdated()
978 if (mItemView->model()) {
979 mItemView->setCurrentIndex(mItemView->model()->index(0, 0));
983 void MainWidget::mergeContacts()
985 const Akonadi::Item::List lst = Utils::collectSelectedContactsItem(mItemView->selectionModel());
986 QPointer<KABMergeContacts::MergeContactsDialog> dlg = new KABMergeContacts::MergeContactsDialog(this);
987 dlg->setContacts(lst);
988 dlg->exec();
989 delete dlg;
992 void MainWidget::slotSearchDuplicateContacts()
994 const Akonadi::Item::List lst = Utils::collectSelectedContactsItem(mItemView->selectionModel());
995 QPointer<KABMergeContacts::SearchAndMergeContactDuplicateContactDialog> dlg = new KABMergeContacts::SearchAndMergeContactDuplicateContactDialog(this);
996 dlg->searchPotentialDuplicateContacts(lst);
997 dlg->exec();
998 delete dlg;
1001 Akonadi::EntityTreeModel *MainWidget::entityTreeModel() const
1003 QAbstractProxyModel *proxy = qobject_cast<QAbstractProxyModel *>(mCollectionView->model());
1004 while (proxy) {
1005 Akonadi::EntityTreeModel *etm = qobject_cast<Akonadi::EntityTreeModel *>(proxy->sourceModel());
1006 if (etm) {
1007 return etm;
1009 proxy = qobject_cast<QAbstractProxyModel *>(proxy->sourceModel());
1012 qCWarning(KADDRESSBOOK_LOG) << "Couldn't find EntityTreeModel";
1013 return Q_NULLPTR;
1016 void MainWidget::slotCheckNewCalendar(const QModelIndex &parent, int begin, int end)
1018 // HACK: Check newly created calendars
1019 Akonadi::EntityTreeModel *etm = entityTreeModel();
1020 if (etm && etm->isCollectionTreeFetched()) {
1021 for (int row = begin; row <= end; ++row) {
1022 QModelIndex index = mCollectionView->model()->index(row, 0, parent);
1023 if (index.isValid()) {
1024 mCollectionView->model()->setData(index, Qt::Checked, Qt::CheckStateRole);
1027 if (parent.isValid()) {
1028 mCollectionView->setExpanded(parent, true);
1033 void MainWidget::slotSendVcards()
1035 const Akonadi::Item::List lst = collectSelectedAllContactsItem(mItemView->selectionModel());
1036 if (!lst.isEmpty()) {
1037 KABSendVCards::SendVcardsJob *sendVcards = new KABSendVCards::SendVcardsJob(lst, this);
1038 connect(sendVcards, &KABSendVCards::SendVcardsJob::sendVCardsError, this, &MainWidget::slotSendVcardsError);
1039 sendVcards->start();
1043 void MainWidget::slotSendVcardsError(const QString &error)
1045 KMessageBox::error(this, error);
1048 void MainWidget::slotSendMail()
1050 const Akonadi::Item::List lst = collectSelectedAllContactsItem(mItemView->selectionModel());
1051 if (!lst.isEmpty()) {
1052 KABMailSender::MailSenderJob *mailSender = new KABMailSender::MailSenderJob(lst, this);
1053 connect(mailSender, &KABMailSender::MailSenderJob::sendMails, this, &MainWidget::slotSendMails);
1054 connect(mailSender, &KABMailSender::MailSenderJob::sendMailsError, this, &MainWidget::slotSendMailsError);
1055 mailSender->start();
1056 } else {
1057 KMessageBox::sorry(this, i18n("You have not selected any contacts."));
1061 void MainWidget::slotSendMailsError(const QString &error)
1063 KMessageBox::error(this, error);
1066 void MainWidget::slotSendMails(const QStringList &emails)
1068 if (!emails.isEmpty()) {
1069 QUrl url;
1070 url.setScheme(QStringLiteral("mailto"));
1071 url.setPath(emails.join(QStringLiteral(";")));
1072 QDesktopServices::openUrl(url);
1076 void MainWidget::slotDebugBaloo()
1078 const Akonadi::Item::List lst = collectSelectedAllContactsItem(mItemView->selectionModel());
1079 if (lst.isEmpty()) {
1080 return;
1082 QPointer<PimCommon::BalooDebugDialog> dlg = new PimCommon::BalooDebugDialog;
1083 dlg->setAkonadiId(lst.at(0).id());
1084 dlg->setAttribute(Qt::WA_DeleteOnClose);
1085 dlg->setSearchType(PimCommon::BalooDebugSearchPathComboBox::Contacts);
1086 dlg->doSearch();
1087 dlg->show();
1090 const Akonadi::Item::List MainWidget::collectSelectedAllContactsItem(QItemSelectionModel *model)
1092 Akonadi::Item::List lst;
1094 const QModelIndexList indexes = model->selectedRows(0);
1095 for (int i = 0; i < indexes.count(); ++i) {
1096 const QModelIndex index = indexes.at(i);
1097 if (index.isValid()) {
1098 const Akonadi::Item item =
1099 index.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
1100 if (item.isValid()) {
1101 if (item.hasPayload<KContacts::Addressee>() || item.hasPayload<KContacts::ContactGroup>()) {
1102 lst.append(item);
1107 return lst;
1110 void MainWidget::slotServerSideSubscription()
1112 Akonadi::Collection collection = currentAddressBook();
1113 if (collection.isValid()) {
1114 PimCommon::ManageServerSideSubscriptionJob *job = new PimCommon::ManageServerSideSubscriptionJob(this);
1115 job->setCurrentCollection(collection);
1116 job->setParentWidget(this);
1117 job->start();
1121 void MainWidget::slotCurrentCollectionChanged(const Akonadi::Collection &col)
1123 mXXPortManager->setDefaultAddressBook(col);
1124 bool isOnline;
1125 mServerSideSubscription->setEnabled(PimCommon::Util::isImapFolder(col, isOnline));
1128 void MainWidget::slotCheckGravatar()
1130 const Akonadi::Item::List lst = collectSelectedAllContactsItem(mItemView->selectionModel());
1131 if (lst.count() == 1) {
1132 Akonadi::Item item = lst.first();
1133 if (item.hasPayload<KContacts::Addressee>()) {
1134 QPointer<KABGravatar::GravatarUpdateDialog> dlg = new KABGravatar::GravatarUpdateDialog(this);
1135 if (dlg->exec()) {
1136 //extract emails.
1138 delete dlg;