Make me trust this output by using the original enums.
[kdepim.git] / kaddressbook / mainwidget.cpp
blobf395e980ed2d221673a3097472a3c35ee4e7e185
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"
23 #include "contactswitcher.h"
24 #include "globalcontactmodel.h"
25 #include <kdescendantsproxymodel.h>
26 #include "modelcolumnmanager.h"
27 #include "printing/printingwizard.h"
28 #include "quicksearchwidget.h"
29 #include "settings.h"
30 #include "xxportmanager.h"
32 #ifdef GRANTLEE_FOUND
33 #include "grantleecontactformatter.h"
34 #include "grantleecontactgroupformatter.h"
35 #endif
37 #include <akonadi/etmviewstatesaver.h>
38 #include <akonadi/collectionfilterproxymodel.h>
39 #include <akonadi/collectionmodel.h>
40 #include <akonadi/contact/contactdefaultactions.h>
41 #include <akonadi/contact/contacteditordialog.h>
42 #include <akonadi/contact/contactgroupeditordialog.h>
43 #include <akonadi/contact/contactgroupviewer.h>
44 #include <akonadi/contact/contactsfilterproxymodel.h>
45 #include <akonadi/contact/contactstreemodel.h>
46 #include <akonadi/contact/contactviewer.h>
47 #include <akonadi/contact/standardcontactactionmanager.h>
48 #include <akonadi/control.h>
49 #include <akonadi/entitymimetypefiltermodel.h>
50 #include <akonadi/entitytreeview.h>
51 #include <akonadi/entitytreeviewstatesaver.h>
52 #include <akonadi/itemview.h>
53 #include <akonadi/mimetypechecker.h>
55 #include <kaction.h>
56 #include <kactioncollection.h>
57 #include <kabc/addressee.h>
58 #include <kabc/contactgroup.h>
59 #include <kapplication.h>
60 #include <kcheckableproxymodel.h>
61 #include <kicon.h>
62 #include <klineedit.h>
63 #include <klocale.h>
64 #include <kselectionproxymodel.h>
65 #include <kstandarddirs.h>
66 #include <ktextbrowser.h>
67 #include <ktoggleaction.h>
68 #include <ktoolbar.h>
69 #include <kxmlguiwindow.h>
70 #include <libkdepim/uistatesaver.h>
72 #include <QtGui/QAction>
73 #include <QtGui/QHBoxLayout>
74 #include <QtGui/QHeaderView>
75 #include <QtGui/QListView>
76 #include <QtGui/QPrinter>
77 #include <QtGui/QPrintDialog>
78 #include <QtGui/QSortFilterProxyModel>
79 #include <QtGui/QSplitter>
80 #include <QtGui/QStackedWidget>
84 namespace {
85 static bool isStructuralCollection( const Akonadi::Collection &collection )
87 QStringList mimeTypes;
88 mimeTypes << KABC::Addressee::mimeType() << KABC::ContactGroup::mimeType();
89 const QStringList collectionMimeTypes = collection.contentMimeTypes();
90 foreach ( const QString &mimeType, mimeTypes ) {
91 if ( collectionMimeTypes.contains( mimeType ) )
92 return false;
94 return true;
97 class StructuralCollectionsNotCheckableProxy : public KCheckableProxyModel {
98 public:
99 StructuralCollectionsNotCheckableProxy(QObject* parent)
100 : KCheckableProxyModel(parent)
103 /* reimp */ QVariant data( const QModelIndex &index, int role ) const
105 if ( !index.isValid() )
106 return QVariant();
108 if ( role == Qt::CheckStateRole ) {
109 // Don't show the checkbox if the collection can't contain incidences
110 const Akonadi::Collection collection = index.data( Akonadi::EntityTreeModel::CollectionRole ).value<Akonadi::Collection>();
111 if ( collection.isValid() && isStructuralCollection( collection ) ) {
112 return QVariant();
115 return KCheckableProxyModel::data( index, role );
121 MainWidget::MainWidget( KXMLGUIClient *guiClient, QWidget *parent )
122 : QWidget( parent ), mAllContactsModel( 0 ), mXmlGuiClient( guiClient )
124 mXXPortManager = new XXPortManager( this );
126 setupGui();
127 setupActions( guiClient->actionCollection() );
130 * The item models, proxies and views have the following structure:
132 * mItemView
135 * mContactsFilterModel
138 * mItemTree
141 * | mAllContactsModel
142 * | ^
143 * | |
144 * mCollectionView selectionProxyModel descendantsModel
145 * ^ ^ ^ ^
146 * | | | |
147 * | selectionModel | |
148 * | | | |
149 * proxyModel ---------' | |
150 * ^ | |
151 * | | |
152 * mCollectionTree | |
153 * ^ | |
154 * | | _______________/
155 * \ / /
156 * GlobalContactModel::instance()
159 * GlobalContactModel::instance(): The global contact model (contains collections and items)
160 * mCollectionTree: Filters out all items
161 * proxyModel: Allows the user to select collections by checkboxes
162 * selectionModel: Represents the selected collections that have been selected in
163 * proxyModel
164 * mCollectionView: Shows the collections (address books) in a view
165 * selectionProxyModel: Filters out all collections and items that are no children
166 * of the collections currently selected in selectionModel
167 * mItemTree: Filters out all collections
168 * mContactsFilterModel: Filters the contacts by the content of mQuickSearchWidget
169 * mItemView: Shows the items (contacts and contact groups) in a view
171 * descendantsModel: Flattens the item/collection tree to a list
172 * mAllContactsModel: Provides a list of all available contacts from all address books
175 mCollectionTree = new Akonadi::EntityMimeTypeFilterModel( this );
176 mCollectionTree->setDynamicSortFilter( true );
177 mCollectionTree->setSortCaseSensitivity( Qt::CaseInsensitive );
178 mCollectionTree->setSourceModel( GlobalContactModel::instance()->model() );
179 mCollectionTree->addMimeTypeInclusionFilter( Akonadi::Collection::mimeType() );
180 mCollectionTree->setHeaderGroup( Akonadi::EntityTreeModel::CollectionTreeHeaders );
182 mCollectionSelectionModel = new QItemSelectionModel( mCollectionTree );
183 StructuralCollectionsNotCheckableProxy *checkableProxyModel = new StructuralCollectionsNotCheckableProxy( this );
184 checkableProxyModel->setSelectionModel( mCollectionSelectionModel );
185 checkableProxyModel->setSourceModel( mCollectionTree );
187 mCollectionView->setModel( checkableProxyModel );
188 mCollectionView->setXmlGuiClient( guiClient );
189 mCollectionView->header()->setDefaultAlignment( Qt::AlignCenter );
190 mCollectionView->header()->setSortIndicatorShown( false );
192 connect( mCollectionView, SIGNAL(currentChanged(Akonadi::Collection)),
193 mXXPortManager, SLOT(setDefaultAddressBook(Akonadi::Collection)) );
195 KSelectionProxyModel *selectionProxyModel = new KSelectionProxyModel( mCollectionSelectionModel,
196 this );
197 selectionProxyModel->setSourceModel( GlobalContactModel::instance()->model() );
198 selectionProxyModel->setFilterBehavior( KSelectionProxyModel::ChildrenOfExactSelection );
200 mItemTree = new Akonadi::EntityMimeTypeFilterModel( this );
201 mItemTree->setSourceModel( selectionProxyModel );
202 mItemTree->addMimeTypeExclusionFilter( Akonadi::Collection::mimeType() );
203 mItemTree->setHeaderGroup( Akonadi::EntityTreeModel::ItemListHeaders );
205 mContactsFilterModel = new Akonadi::ContactsFilterProxyModel( this );
206 mContactsFilterModel->setSourceModel( mItemTree );
207 connect( mQuickSearchWidget, SIGNAL(filterStringChanged(QString)),
208 mContactsFilterModel, SLOT(setFilterString(QString)) );
209 connect( mQuickSearchWidget, SIGNAL(filterStringChanged(QString)),
210 this, SLOT(selectFirstItem()) );
211 connect( mQuickSearchWidget, SIGNAL(arrowDownKeyPressed()),
212 mItemView, SLOT(setFocus()) );
214 mItemView->setModel( mContactsFilterModel );
215 mItemView->setXmlGuiClient( guiClient );
216 mItemView->setSelectionMode( QAbstractItemView::ExtendedSelection );
217 mItemView->setRootIsDecorated( false );
218 mItemView->header()->setDefaultAlignment( Qt::AlignCenter );
220 mXXPortManager->setSelectionModel( mItemView->selectionModel() );
222 mActionManager = new Akonadi::StandardContactActionManager( guiClient->actionCollection(), this );
223 mActionManager->setCollectionSelectionModel( mCollectionView->selectionModel() );
224 mActionManager->setItemSelectionModel( mItemView->selectionModel() );
225 mActionManager->createAllActions();
227 connect( mItemView, SIGNAL(currentChanged(Akonadi::Item)),
228 this, SLOT(itemSelected(Akonadi::Item)) );
229 connect( mItemView, SIGNAL(doubleClicked(Akonadi::Item)),
230 mActionManager->action( Akonadi::StandardContactActionManager::EditItem ), SLOT(trigger()) );
231 connect( mItemView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
232 this, SLOT(itemSelectionChanged(QModelIndex,QModelIndex)) );
234 // show the contact details view as default
235 mDetailsViewStack->setCurrentWidget( mContactDetails );
237 mContactSwitcher->setView( mItemView );
239 Akonadi::Control::widgetNeedsAkonadi( this );
241 mModelColumnManager = new ModelColumnManager( GlobalContactModel::instance()->model(), this );
242 mModelColumnManager->setWidget( mItemView->header() );
243 mModelColumnManager->load();
245 QMetaObject::invokeMethod( this, "delayedInit", Qt::QueuedConnection );
248 void MainWidget::delayedInit()
250 // restore previous state
252 const KConfigGroup group( Settings::self()->config(), "UiState_MainWidgetSplitter" );
253 KPIM::UiStateSaver::restoreState( mMainWidgetSplitter, group );
256 const KConfigGroup group( Settings::self()->config(), "UiState_ContactView" );
257 KPIM::UiStateSaver::restoreState( mItemView, group );
260 mXmlGuiClient->actionCollection()->action( "options_show_simplegui" )->setChecked( Settings::self()->useSimpleMode() );
262 connect( GlobalContactModel::instance()->model(), SIGNAL(modelAboutToBeReset()), SLOT(saveState()) );
263 connect( GlobalContactModel::instance()->model(), SIGNAL(modelReset()), SLOT(restoreState()) );
264 connect( kapp, SIGNAL(aboutToQuit()), SLOT(saveState()) );
266 restoreState();
269 MainWidget::~MainWidget()
271 mModelColumnManager->store();
274 if ( !Settings::self()->useSimpleMode() ) {
275 // Do not save the splitter values when in simple mode, because we can't
276 // restore them correctly when switching back to normal mode
278 KConfigGroup group( Settings::self()->config(), "UiState_MainWidgetSplitter" );
279 KPIM::UiStateSaver::saveState( mMainWidgetSplitter, group );
283 KConfigGroup group( Settings::self()->config(), "UiState_ContactView" );
284 KPIM::UiStateSaver::saveState( mItemView, group );
287 saveState();
289 Settings::self()->writeConfig();
292 void MainWidget::restoreState()
294 // collection view
296 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
297 saver->setView( mCollectionView );
299 const KConfigGroup group( Settings::self()->config(), "CollectionViewState" );
300 saver->restoreState( group );
303 // collection view
305 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
306 saver->setSelectionModel( mCollectionSelectionModel );
308 const KConfigGroup group( Settings::self()->config(), "CollectionViewCheckState" );
309 saver->restoreState( group );
312 // item view
314 Akonadi::ETMViewStateSaver *saver = new Akonadi::ETMViewStateSaver;
315 saver->setView( mItemView );
316 saver->setSelectionModel( mItemView->selectionModel() );
318 const KConfigGroup group( Settings::self()->config(), "ItemViewState" );
319 saver->restoreState( group );
323 void MainWidget::saveState()
325 // collection view
327 Akonadi::ETMViewStateSaver saver;
328 saver.setView( mCollectionView );
330 KConfigGroup group( Settings::self()->config(), "CollectionViewState" );
331 saver.saveState( group );
332 group.sync();
335 // collection view
337 Akonadi::ETMViewStateSaver saver;
338 saver.setSelectionModel( mCollectionSelectionModel );
340 KConfigGroup group( Settings::self()->config(), "CollectionViewCheckState" );
341 saver.saveState( group );
342 group.sync();
345 // item view
347 Akonadi::ETMViewStateSaver saver;
348 saver.setView( mItemView );
349 saver.setSelectionModel( mItemView->selectionModel() );
351 KConfigGroup group( Settings::self()->config(), "ItemViewState" );
352 saver.saveState( group );
353 group.sync();
357 void MainWidget::setupGui()
359 // the horizontal main layout
360 QHBoxLayout *layout = new QHBoxLayout( this );
361 layout->setMargin( 0 );
363 // The splitter that contains the three main parts of the gui
364 // - collection view on the left
365 // - item view in the middle
366 // - details pane on the right, that contains
367 // - details view stack on the top
368 // - contact switcher at the bottom
369 mMainWidgetSplitter = new QSplitter;
370 mMainWidgetSplitter->setObjectName( "MainWidgetSplitter" );
372 layout->addWidget( mMainWidgetSplitter );
374 // the collection view
375 mCollectionView = new Akonadi::EntityTreeView();
376 mMainWidgetSplitter->addWidget( mCollectionView );
378 // the items view
379 mItemView = new Akonadi::EntityTreeView();
380 mItemView->setObjectName( "ContactView" );
381 mMainWidgetSplitter->addWidget( mItemView );
383 // the details pane that contains the details view stack and contact switcher
384 mDetailsPane = new QWidget;
385 mMainWidgetSplitter->addWidget( mDetailsPane );
387 QVBoxLayout *detailsPaneLayout = new QVBoxLayout( mDetailsPane );
388 detailsPaneLayout->setMargin( 0 );
390 // the details view stack
391 mDetailsViewStack = new QStackedWidget();
392 detailsPaneLayout->addWidget( mDetailsViewStack );
394 // the details widget for contacts
395 mContactDetails = new Akonadi::ContactViewer( mDetailsViewStack );
396 mDetailsViewStack->addWidget( mContactDetails );
398 // the details widget for contact groups
399 mContactGroupDetails = new Akonadi::ContactGroupViewer( mDetailsViewStack );
400 mDetailsViewStack->addWidget( mContactGroupDetails );
402 // the details widget for empty items
403 mEmptyDetails = new KTextBrowser( mDetailsViewStack );
404 mDetailsViewStack->addWidget( mEmptyDetails );
406 // the contact switcher for the simple gui mode
407 mContactSwitcher = new ContactSwitcher;
408 detailsPaneLayout->addWidget( mContactSwitcher );
409 mContactSwitcher->setVisible( false );
411 // the quick search widget which is embedded in the toolbar action
412 mQuickSearchWidget = new QuickSearchWidget;
414 // setup the default actions
415 Akonadi::ContactDefaultActions *actions = new Akonadi::ContactDefaultActions( this );
416 actions->connectToView( mContactDetails );
417 actions->connectToView( mContactGroupDetails );
419 #if 0 // disabled because Grantlee supports no i18n for KDE 4.6 yet
420 Akonadi::GrantleeContactFormatter *formatter =
421 new Akonadi::GrantleeContactFormatter( KStandardDirs::locate( "data", QLatin1String( "kaddressbook/viewertemplates/" ) ) );
423 mContactDetails->setContactFormatter( formatter );
425 Akonadi::GrantleeContactGroupFormatter *groupFormatter =
426 new Akonadi::GrantleeContactGroupFormatter( KStandardDirs::locate( "data", QLatin1String( "kaddressbook/viewertemplates/" ) ) );
428 mContactGroupDetails->setContactGroupFormatter( groupFormatter );
429 #endif
432 void MainWidget::setupActions( KActionCollection *collection )
434 KAction *action = 0;
435 KToggleAction *toggleAction = 0;
437 action = KStandardAction::print( this, SLOT(print()), collection );
438 action->setWhatsThis( i18n( "Print the complete address book or a selected number of contacts." ) );
440 action = collection->addAction( "quick_search" );
441 action->setText( i18n( "Quick search" ) );
442 action->setDefaultWidget( mQuickSearchWidget );
444 action = collection->addAction( "select_all" );
445 action->setText( i18n( "Select All" ) );
446 action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_A ) );
447 action->setWhatsThis( i18n( "Select all contacts in the current address book view." ) );
448 connect( action, SIGNAL(triggered(bool)), mItemView, SLOT(selectAll()) );
450 toggleAction = collection->add<KToggleAction>( "options_show_simplegui" );
451 toggleAction->setText( i18n( "Show Simple View" ) );
452 action->setWhatsThis( i18n( "Show a simple mode of the address book view." ) );
453 connect( toggleAction, SIGNAL(toggled(bool)), SLOT(setSimpleGuiMode(bool)) );
455 // import actions
456 action = collection->addAction( "file_import_vcard" );
457 action->setText( i18n( "Import vCard..." ) );
458 action->setWhatsThis( i18n( "Import contacts from a vCard file." ) );
459 mXXPortManager->addImportAction( action, "vcard30" );
461 action = collection->addAction( "file_import_csv" );
462 action->setText( i18n( "Import CSV file..." ) );
463 action->setWhatsThis( i18n( "Import contacts from a file in comma separated value format." ) );
464 mXXPortManager->addImportAction( action, "csv" );
466 action = collection->addAction( "file_import_ldif" );
467 action->setText( i18n( "Import LDIF file..." ) );
468 action->setWhatsThis( i18n( "Import contacts from an LDIF file." ) );
469 mXXPortManager->addImportAction( action, "ldif" );
471 action = collection->addAction( "file_import_ldap" );
472 action->setText( i18n( "Import from LDAP server..." ) );
473 action->setWhatsThis( i18n( "Import contacts from an LDAP server." ) );
474 mXXPortManager->addImportAction( action, "ldap" );
476 action = collection->addAction( "file_import_gmx" );
477 action->setText( i18n( "Import GMX file..." ) );
478 action->setWhatsThis( i18n( "Import contacts from a GMX address book file." ) );
479 mXXPortManager->addImportAction( action, "gmx" );
482 // export actions
483 action = collection->addAction( "file_export_vcard30" );
484 action->setText( i18n( "Export vCard 3.0..." ) );
485 action->setWhatsThis( i18n( "Export contacts to a vCard 3.0 file." ) );
486 mXXPortManager->addExportAction( action, "vcard30" );
488 action = collection->addAction( "file_export_vcard21" );
489 action->setText( i18n( "Export vCard 2.1..." ) );
490 action->setWhatsThis( i18n( "Export contacts to a vCard 2.1 file." ) );
491 mXXPortManager->addExportAction( action, "vcard21" );
493 action = collection->addAction( "file_export_csv" );
494 action->setText( i18n( "Export CSV file..." ) );
495 action->setWhatsThis( i18n( "Export contacts to a file in comma separated value format." ) );
496 mXXPortManager->addExportAction( action, "csv" );
498 action = collection->addAction( "file_export_ldif" );
499 action->setText( i18n( "Export LDIF file..." ) );
500 action->setWhatsThis( i18n( "Export contacts to an LDIF file." ) );
501 mXXPortManager->addExportAction( action, "ldif" );
503 action = collection->addAction( "file_export_gmx" );
504 action->setText( i18n( "Export GMX file..." ) );
505 action->setWhatsThis( i18n( "Export contacts to a GMX address book file." ) );
506 mXXPortManager->addExportAction( action, "gmx" );
509 void MainWidget::print()
511 QPrinter printer;
512 printer.setDocName( i18n( "Address Book" ) );
513 printer.setOutputFileName( "addressbook.pdf" );
514 printer.setOutputFormat( QPrinter::PdfFormat );
515 printer.setCollateCopies( true );
517 QPrintDialog printDialog( &printer, this );
518 printDialog.setWindowTitle( i18n( "Print Contacts" ) );
519 if ( !printDialog.exec() ) //krazy:exclude=crashy
520 return;
522 KABPrinting::PrintingWizard wizard( &printer, mItemView->selectionModel(), this );
523 wizard.setDefaultAddressBook( currentAddressBook() );
525 wizard.exec();
528 void MainWidget::newContact()
530 mActionManager->action( Akonadi::StandardContactActionManager::CreateContact )->trigger();
533 void MainWidget::newGroup()
535 mActionManager->action( Akonadi::StandardContactActionManager::CreateContactGroup )->trigger();
539 * Depending on the mime type of the selected item, this method
540 * brings up the right view on the detail view stack and sets the
541 * selected item on it.
543 void MainWidget::itemSelected( const Akonadi::Item &item )
545 if ( Akonadi::MimeTypeChecker::isWantedItem( item, KABC::Addressee::mimeType() ) ) {
546 mDetailsViewStack->setCurrentWidget( mContactDetails );
547 mContactDetails->setContact( item );
548 } else if ( Akonadi::MimeTypeChecker::isWantedItem( item, KABC::ContactGroup::mimeType() ) ) {
549 mDetailsViewStack->setCurrentWidget( mContactGroupDetails );
550 mContactGroupDetails->setContactGroup( item );
555 * Catch when the selection has gone ( e.g. an empty address book has been selected )
556 * clear the details view in this case.
558 void MainWidget::itemSelectionChanged( const QModelIndex &current, const QModelIndex& )
560 if ( !current.isValid() )
561 mDetailsViewStack->setCurrentWidget( mEmptyDetails );
564 void MainWidget::selectFirstItem()
566 // Whenever the quick search has changed, we select the first item
567 // in the item view, so that the detailsview is updated
568 if ( mItemView && mItemView->selectionModel() ) {
569 mItemView->selectionModel()->setCurrentIndex( mItemView->model()->index( 0, 0 ),
570 QItemSelectionModel::Rows |
571 QItemSelectionModel::ClearAndSelect );
575 void MainWidget::setSimpleGuiMode( bool on )
577 mCollectionView->setVisible( !on );
578 mItemView->setVisible( !on );
579 mDetailsPane->setVisible( true );
580 mContactSwitcher->setVisible( on );
582 if ( mItemView->model() )
583 mItemView->setCurrentIndex( mItemView->model()->index( 0, 0 ) );
585 Settings::self()->setUseSimpleMode( on );
588 Akonadi::Collection MainWidget::currentAddressBook() const
590 if ( mCollectionView->selectionModel() && mCollectionView->selectionModel()->hasSelection() ) {
591 const QModelIndex index = mCollectionView->selectionModel()->selectedIndexes().first();
592 const Akonadi::Collection collection = index.data( Akonadi::EntityTreeModel::CollectionRole )
593 .value<Akonadi::Collection>();
595 return collection;
598 return Akonadi::Collection();
601 QAbstractItemModel* MainWidget::allContactsModel()
603 if ( !mAllContactsModel ) {
604 KDescendantsProxyModel *descendantsModel = new KDescendantsProxyModel( this );
605 descendantsModel->setSourceModel( GlobalContactModel::instance()->model() );
607 mAllContactsModel = new Akonadi::EntityMimeTypeFilterModel( this );
608 mAllContactsModel->setSourceModel( descendantsModel );
609 mAllContactsModel->addMimeTypeExclusionFilter( Akonadi::Collection::mimeType() );
610 mAllContactsModel->setHeaderGroup( Akonadi::EntityTreeModel::ItemListHeaders );
613 return mAllContactsModel;
616 #include "mainwidget.moc"