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"
30 #include "xxportmanager.h"
33 #include "grantleecontactformatter.h"
34 #include "grantleecontactgroupformatter.h"
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>
56 #include <kactioncollection.h>
57 #include <kabc/addressee.h>
58 #include <kabc/contactgroup.h>
59 #include <kapplication.h>
60 #include <kcheckableproxymodel.h>
62 #include <klineedit.h>
64 #include <kselectionproxymodel.h>
65 #include <kstandarddirs.h>
66 #include <ktextbrowser.h>
67 #include <ktoggleaction.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>
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
) )
97 class StructuralCollectionsNotCheckableProxy
: public KCheckableProxyModel
{
99 StructuralCollectionsNotCheckableProxy(QObject
* parent
)
100 : KCheckableProxyModel(parent
)
103 /* reimp */ QVariant
data( const QModelIndex
&index
, int role
) const
105 if ( !index
.isValid() )
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
) ) {
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 );
127 setupActions( guiClient
->actionCollection() );
130 * The item models, proxies and views have the following structure:
135 * mContactsFilterModel
141 * | mAllContactsModel
144 * mCollectionView selectionProxyModel descendantsModel
147 * | selectionModel | |
149 * proxyModel ---------' | |
152 * mCollectionTree | |
154 * | | _______________/
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
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
,
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()) );
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
);
289 Settings::self()->writeConfig();
292 void MainWidget::restoreState()
296 Akonadi::ETMViewStateSaver
*saver
= new Akonadi::ETMViewStateSaver
;
297 saver
->setView( mCollectionView
);
299 const KConfigGroup
group( Settings::self()->config(), "CollectionViewState" );
300 saver
->restoreState( group
);
305 Akonadi::ETMViewStateSaver
*saver
= new Akonadi::ETMViewStateSaver
;
306 saver
->setSelectionModel( mCollectionSelectionModel
);
308 const KConfigGroup
group( Settings::self()->config(), "CollectionViewCheckState" );
309 saver
->restoreState( group
);
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()
327 Akonadi::ETMViewStateSaver saver
;
328 saver
.setView( mCollectionView
);
330 KConfigGroup
group( Settings::self()->config(), "CollectionViewState" );
331 saver
.saveState( group
);
337 Akonadi::ETMViewStateSaver saver
;
338 saver
.setSelectionModel( mCollectionSelectionModel
);
340 KConfigGroup
group( Settings::self()->config(), "CollectionViewCheckState" );
341 saver
.saveState( group
);
347 Akonadi::ETMViewStateSaver saver
;
348 saver
.setView( mItemView
);
349 saver
.setSelectionModel( mItemView
->selectionModel() );
351 KConfigGroup
group( Settings::self()->config(), "ItemViewState" );
352 saver
.saveState( group
);
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
);
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
);
432 void MainWidget::setupActions( KActionCollection
*collection
)
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)) );
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" );
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()
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
522 KABPrinting::PrintingWizard
wizard( &printer
, mItemView
->selectionModel(), this );
523 wizard
.setDefaultAddressBook( currentAddressBook() );
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
¤t
, 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
>();
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"