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"
30 #include "xxport/xxportmanager.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>
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"
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>
98 #include <QPrintDialog>
100 #include <QStackedWidget>
101 #include <QDBusConnection>
102 #include <QDesktopServices>
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
)) {
119 class StructuralCollectionsNotCheckableProxy
: public KCheckableProxyModel
122 explicit StructuralCollectionsNotCheckableProxy(QObject
*parent
)
123 : KCheckableProxyModel(parent
)
127 QVariant
data(const QModelIndex
&index
, int role
) const Q_DECL_OVERRIDE
129 if (!index
.isValid()) {
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
)) {
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
>();
158 setupActions(guiClient
->actionCollection());
161 * The item models, proxies and views have the following structure:
166 * mContactsFilterModel
169 * mCategorySelectWidget --> mCategoryFilterModel
175 * | mAllContactsModel
178 * mCollectionView selectionProxyModel descendantsModel
181 * | selectionModel | |
183 * proxyModel ---------' | |
186 * mCollectionTree | |
188 * | | _______________/
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
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
),
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"));
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)
384 action(QStringLiteral("options_show_qrcodes"))->setChecked(showQRCodes());
387 connect(GlobalContactModel::instance()->model(), SIGNAL(modelAboutToBeReset()),
389 connect(GlobalContactModel::instance()->model(), SIGNAL(modelReset()),
390 SLOT(restoreState()));
391 connect(qApp
, &QApplication::aboutToQuit
, this, &MainWidget::saveState
);
394 updateQuickSearchText();
397 MainWidget::~MainWidget()
399 mModelColumnManager
->store();
400 saveSplitterStates();
402 KConfigGroup
group(Settings::self()->config(), "UiState_ContactView");
403 KPIM::UiStateSaver::saveState(mItemView
, group
);
406 delete mGrantleeThemeManager
;
408 Settings::self()->save();
411 void MainWidget::restoreState()
415 Akonadi::ETMViewStateSaver
*saver
= new Akonadi::ETMViewStateSaver
;
416 saver
->setView(mCollectionView
);
418 const KConfigGroup
group(Settings::self()->config(), "CollectionViewState");
419 saver
->restoreState(group
);
424 Akonadi::ETMViewStateSaver
*saver
= new Akonadi::ETMViewStateSaver
;
425 saver
->setSelectionModel(mCollectionSelectionModel
);
427 const KConfigGroup
group(Settings::self()->config(), "CollectionViewCheckState");
428 saver
->restoreState(group
);
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()
446 Akonadi::ETMViewStateSaver saver
;
447 saver
.setView(mCollectionView
);
449 KConfigGroup
group(Settings::self()->config(), "CollectionViewState");
450 saver
.saveState(group
);
456 Akonadi::ETMViewStateSaver saver
;
457 saver
.setSelectionModel(mCollectionSelectionModel
);
459 KConfigGroup
group(Settings::self()->config(), "CollectionViewCheckState");
460 saver
.saveState(group
);
466 Akonadi::ETMViewStateSaver saver
;
467 saver
.setView(mItemView
);
468 saver
.setSelectionModel(mItemView
->selectionModel());
470 KConfigGroup
group(Settings::self()->config(), "ItemViewState");
471 saver
.saveState(group
);
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
);
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
);
612 mViewModeGroup
= new QActionGroup(this);
614 QAction
*act
= new QAction(i18nc("@action:inmenu", "Simple (one column)"), mViewModeGroup
);
615 act
->setCheckable(true);
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);
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);
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
*)));
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"));
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();
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()
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();
751 Settings::self()->setDefaultFileName(printer
.outputFileName());
752 Settings::self()->setPrintingStyle(wizard
.printingStyle());
753 Settings::self()->setSortOrder(wizard
.sortOrder());
758 void MainWidget::print()
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
) {
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
¤t
, 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);
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));
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
>();
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
898 mode
= currentMode
;// initialisation, no save
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);
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) {
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) {
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
);
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
);
1001 Akonadi::EntityTreeModel
*MainWidget::entityTreeModel() const
1003 QAbstractProxyModel
*proxy
= qobject_cast
<QAbstractProxyModel
*>(mCollectionView
->model());
1005 Akonadi::EntityTreeModel
*etm
= qobject_cast
<Akonadi::EntityTreeModel
*>(proxy
->sourceModel());
1009 proxy
= qobject_cast
<QAbstractProxyModel
*>(proxy
->sourceModel());
1012 qCWarning(KADDRESSBOOK_LOG
) << "Couldn't find EntityTreeModel";
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();
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()) {
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()) {
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
);
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
>()) {
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);
1121 void MainWidget::slotCurrentCollectionChanged(const Akonadi::Collection
&col
)
1123 mXXPortManager
->setDefaultAddressBook(col
);
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);