Expand the collection view, initially.
[kdepim.git] / korganizer / akonadicollectionview.cpp
blob210477a51b3e08875d302cb050a68f1b7e551be7
1 /*
2 This file is part of KOrganizer.
4 Copyright (c) 2003,2004 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 Copyright (C) 2009 Sebastian Sauer <sebsauer@kdab.net>
7 Copyright (C) 2010 Laurent Montel <montel@kde.org>
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License along
20 with this program; if not, write to the Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 As a special exception, permission is given to link this program
24 with any edition of Qt, and distribute the resulting executable,
25 without including the source code for Qt in the source distribution.
28 #include "akonadicollectionview.h"
29 #include <kcolordialog.h>
30 #include "kocore.h"
31 #include "kohelper.h"
32 #include "koprefs.h"
34 #include <calendarsupport/calendarmodel.h>
35 #include <calendarsupport/collectionselection.h>
36 #include <calendarsupport/kcalprefs.h>
37 #include <calendarsupport/utils.h>
39 #include <KLineEdit>
40 #include <KDebug>
41 #include <KDialog>
42 #include <KAction>
43 #include <KActionCollection>
44 #include <krecursivefilterproxymodel.h>
45 #include <kjob.h>
46 #include <QVBoxLayout>
47 #include <QHeaderView>
48 #include <QItemSelectionModel>
50 #include <akonadi_next/kcheckableproxymodel.h>
52 #include <akonadi/calendar/standardcalendaractionmanager.h>
53 #include <akonadi/collection.h>
54 #include <akonadi/collectionview.h>
55 #include <akonadi/collectionfilterproxymodel.h>
56 #include <akonadi/collectiondeletejob.h>
57 #include <akonadi/entitytreemodel.h>
58 #include <akonadi/entitytreeview.h>
59 #include <akonadi/entitydisplayattribute.h>
60 #include <akonadi/agenttypedialog.h>
61 #include <akonadi/agentinstancewidget.h>
62 #include <akonadi/agentinstancecreatejob.h>
63 #include <akonadi/agentfilterproxymodel.h>
64 #include <akonadi/control.h>
65 #include <akonadi/session.h>
66 #include <akonadi/changerecorder.h>
67 #include <akonadi/agentmanager.h>
68 #include <akonadi/agentinstance.h>
70 #include <QHash>
72 using namespace Future;
74 AkonadiCollectionViewFactory::AkonadiCollectionViewFactory( CalendarView *view )
75 : mView( view ), mAkonadiCollectionView( 0 )
79 namespace {
80 class ColorProxyModel : public QSortFilterProxyModel
82 public:
83 explicit ColorProxyModel( QObject* parent=0 )
84 : QSortFilterProxyModel( parent ),
85 mInitDefaultCalendar( false )
89 /* reimp */ QVariant data( const QModelIndex &index, int role ) const
91 if ( !index.isValid() )
92 return QVariant();
93 if ( role == Qt::DecorationRole ) {
94 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
96 if ( !collection.contentMimeTypes().isEmpty() ) {
97 if ( collection.hasAttribute<Akonadi::EntityDisplayAttribute>() &&
98 !collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName().isEmpty() ) {
99 return collection.attribute<Akonadi::EntityDisplayAttribute>()->icon();
100 } else {
101 QColor col = KOHelper::resourceColor( collection );
102 return col.isValid() ? col : QVariant();
105 } else if ( role == Qt::FontRole ) {
106 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
107 if ( !collection.contentMimeTypes().isEmpty() && KOHelper::isStandardCalendar( collection.id() ) &&
108 collection.rights() & Akonadi::Collection::CanCreateItem) {
109 QFont font = qvariant_cast<QFont>( QSortFilterProxyModel::data( index, Qt::FontRole ) );
110 font.setBold( true );
111 if ( !mInitDefaultCalendar ) {
112 mInitDefaultCalendar = true;
113 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( collection.id() );
115 return font;
118 return QSortFilterProxyModel::data( index, role );
120 private:
121 mutable bool mInitDefaultCalendar;
125 CalendarViewExtension *AkonadiCollectionViewFactory::create( QWidget *parent )
127 mAkonadiCollectionView = new AkonadiCollectionView( view(), true, parent );
128 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesChanged(bool)),
129 mView, SLOT(resourcesChanged()) );
130 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesChanged(bool)),
131 mView, SLOT(updateCategories()) );
132 QObject::connect( mAkonadiCollectionView, SIGNAL( resourcesAddedRemoved() ),
133 mView, SLOT( resourcesChanged() ) );
134 QObject::connect( mAkonadiCollectionView, SIGNAL( resourcesAddedRemoved() ),
135 mView, SLOT( updateCategories() ) );
136 return mAkonadiCollectionView;
139 CalendarView* AkonadiCollectionViewFactory::view() const
141 return mView;
144 AkonadiCollectionView* AkonadiCollectionViewFactory::collectionView() const
146 return mAkonadiCollectionView;
149 AkonadiCollectionView::AkonadiCollectionView( CalendarView* view, bool hasContextMenu, QWidget *parent )
150 : CalendarViewExtension( parent ),
151 mActionManager(0),
152 mCollectionview(0),
153 mBaseModel( 0 ),
154 mSelectionProxyModel( 0 ),
155 mNotSendAddRemoveSignal( false ),
156 mWasDefaultCalendar( false ),
157 mInitDefaultCalendar( false ),
158 mHasContextMenu( hasContextMenu )
160 QVBoxLayout *topLayout = new QVBoxLayout( this );
161 topLayout->setMargin( 0 );
162 topLayout->setSpacing( KDialog::spacingHint() );
164 KLineEdit *searchCol = new KLineEdit( this );
165 searchCol->setClearButtonShown( true );
166 searchCol->setClickMessage( i18nc( "@info/plain Displayed grayed-out inside the "
167 "textbox, verb to search", "Search" ) );
168 topLayout->addWidget( searchCol );
171 Akonadi::CollectionFilterProxyModel *collectionproxymodel = new Akonadi::CollectionFilterProxyModel( this );
172 collectionproxymodel->setObjectName( "Only show collections" );
173 collectionproxymodel->setDynamicSortFilter( true );
174 collectionproxymodel->addMimeTypeFilter( QString::fromLatin1( "text/calendar" ) );
175 //collectionproxymodel->addExcludedSpecialResources(Akonadi::Collection::SearchResource);
177 ColorProxyModel* colorProxy = new ColorProxyModel( this );
178 colorProxy->setObjectName( "Show calendar colors" );
179 colorProxy->setDynamicSortFilter( true );
180 colorProxy->setSourceModel( collectionproxymodel );
181 mBaseModel = collectionproxymodel;
183 mCollectionview = new Akonadi::EntityTreeView( this );
184 topLayout->addWidget( mCollectionview );
185 mCollectionview->header()->hide();
186 mCollectionview->setRootIsDecorated( true );
188 //Filter tree view.
189 KRecursiveFilterProxyModel* filterTreeViewModel = new KRecursiveFilterProxyModel( this );
190 filterTreeViewModel->setDynamicSortFilter( true );
191 filterTreeViewModel->setSourceModel( colorProxy );
192 filterTreeViewModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
193 filterTreeViewModel->setObjectName( "Recursive filtering, for the search bar" );
194 mCollectionview->setModel( filterTreeViewModel );
196 connect( searchCol, SIGNAL( textChanged(QString) ), filterTreeViewModel, SLOT( setFilterFixedString(QString) ) );
198 connect( mCollectionview->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
199 this, SLOT(selectionChanged()) );
201 connect( mBaseModel, SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
202 this, SLOT( rowsInserted( const QModelIndex&, int, int ) ) );
204 //mCollectionview->setSelectionMode( QAbstractItemView::NoSelection );
205 KXMLGUIClient *xmlclient = KOCore::self()->xmlguiClient( view );
206 if ( xmlclient ) {
207 mCollectionview->setXmlGuiClient( xmlclient );
209 mActionManager = new Akonadi::StandardCalendarActionManager( xmlclient->actionCollection(), mCollectionview );
210 mActionManager->createAllActions();
211 mActionManager->setCollectionSelectionModel( mCollectionview->selectionModel() );
213 mActionManager->interceptAction( Akonadi::StandardActionManager::CreateResource );
214 mActionManager->interceptAction( Akonadi::StandardActionManager::DeleteResources );
215 mActionManager->interceptAction( Akonadi::StandardActionManager::DeleteCollections );
217 connect( mActionManager->action( Akonadi::StandardActionManager::CreateResource ), SIGNAL( triggered( bool ) ),
218 this, SLOT( newCalendar() ) );
219 connect( mActionManager->action( Akonadi::StandardActionManager::DeleteResources ), SIGNAL( triggered( bool ) ),
220 this, SLOT( deleteCalendar() ) );
221 connect( mActionManager->action( Akonadi::StandardActionManager::DeleteCollections ), SIGNAL( triggered( bool ) ),
222 this, SLOT( deleteCalendar() ) );
224 mActionManager->setContextText( Akonadi::StandardActionManager::CollectionProperties, Akonadi::StandardActionManager::DialogTitle,
225 i18nc( "@title:window", "Properties of Calendar Folder %1" ) );
227 const QStringList pages = QStringList() << QLatin1String( "CalendarSupport::CollectionGeneralPage" )
228 << QLatin1String( "Akonadi::CachePolicyPage" );
230 mActionManager->setCollectionPropertiesPageNames( pages );
232 mDisableColor = new KAction( mCollectionview );
233 mDisableColor->setText( "&Disable Color");
234 mDisableColor->setEnabled( false );
235 xmlclient->actionCollection()->addAction( QString::fromLatin1( "disable_color" ), mDisableColor );
236 connect( mDisableColor, SIGNAL( triggered( bool ) ), this, SLOT(disableColor() ) );
238 mAssignColor = new KAction( mCollectionview );
239 mAssignColor->setText( i18n( "&Assign Color..." ) );
240 mAssignColor->setEnabled( false );
241 xmlclient->actionCollection()->addAction( QString::fromLatin1( "assign_color" ), mAssignColor );
242 connect( mAssignColor, SIGNAL( triggered( bool ) ), this, SLOT(assignColor()) );
244 mDefaultCalendar = new KAction( mCollectionview );
245 mDefaultCalendar->setText( i18n( "Use as &Default Calendar" ) );
246 mDefaultCalendar->setEnabled( false );
247 xmlclient->actionCollection()->addAction( QString::fromLatin1( "set_standard_calendar" ),mDefaultCalendar );
248 connect( mDefaultCalendar, SIGNAL( triggered( bool ) ), this, SLOT( setDefaultCalendar()) );
250 mCollectionview->expandAll();
253 AkonadiCollectionView::~AkonadiCollectionView()
257 void AkonadiCollectionView::setDefaultCalendar()
259 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
260 Q_ASSERT( index.isValid() );
261 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
262 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( collection.id() );
263 CalendarSupport::KCalPrefs::instance()->usrWriteConfig();
264 updateMenu();
265 updateView();
267 emit defaultResourceChanged( collection );
270 void AkonadiCollectionView::assignColor()
272 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
273 Q_ASSERT( index.isValid() );
274 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
275 Q_ASSERT( collection.isValid() );
277 const QString identifier = QString::number( collection.id() );
278 const QColor defaultColor = KOPrefs::instance()->resourceColor( identifier );
279 QColor myColor;
280 const int result = KColorDialog::getColor( myColor, defaultColor );
281 if ( result == KColorDialog::Accepted && myColor != defaultColor ) {
282 KOPrefs::instance()->setResourceColor( identifier, myColor );
283 emit colorsChanged();
284 updateMenu();
285 updateView();
289 void AkonadiCollectionView::disableColor()
291 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
292 Q_ASSERT( index.isValid() );
293 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
294 Q_ASSERT( collection.isValid() );
295 const QString identifier = QString::number( collection.id() );
296 KOPrefs::instance()->setResourceColor( identifier, QColor() );
297 updateMenu();
298 updateView();
299 emit colorsChanged();
302 void AkonadiCollectionView::setCollectionSelectionProxyModel( KCheckableProxyModel* m )
304 if ( mSelectionProxyModel == m )
305 return;
306 mSelectionProxyModel = m;
307 if ( !mSelectionProxyModel )
308 return;
309 mBaseModel->setSourceModel( mSelectionProxyModel );
310 connect( mSelectionProxyModel->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged()) );
313 KCheckableProxyModel *AkonadiCollectionView::collectionSelectionProxyModel() const
315 return mSelectionProxyModel;
318 Akonadi::EntityTreeView* AkonadiCollectionView::view() const
320 return mCollectionview;
323 void AkonadiCollectionView::updateView()
325 emit resourcesChanged( mSelectionProxyModel ? mSelectionProxyModel->selectionModel()->hasSelection() : false );
328 void AkonadiCollectionView::updateMenu()
330 if ( !mHasContextMenu ) {
331 return;
333 bool enableAction = mCollectionview->selectionModel()->hasSelection();
334 enableAction = enableAction && ( KOPrefs::instance()->agendaViewColors() != KOPrefs::CategoryOnly );
335 mAssignColor->setEnabled( enableAction );
336 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
338 bool disableStuff = false;
340 if ( index.isValid() ) {
341 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
342 Q_ASSERT( collection.isValid() );
344 if ( !collection.contentMimeTypes().isEmpty() ) {
345 const QString identifier = QString::number( collection.id() );
346 const QColor defaultColor = KOPrefs::instance()->resourceColor( identifier );
347 enableAction = enableAction && defaultColor.isValid();
348 mDisableColor->setEnabled( enableAction );
349 mDefaultCalendar->setEnabled( !KOHelper::isStandardCalendar( collection.id() ) &&
350 collection.rights() & Akonadi::Collection::CanCreateItem );
351 } else {
352 disableStuff = true;
354 } else {
355 disableStuff = true;
358 if ( disableStuff ) {
359 mDisableColor->setEnabled( false );
360 mDefaultCalendar->setEnabled( false );
361 mAssignColor->setEnabled( false );
365 void AkonadiCollectionView::selectionChanged()
367 updateMenu();
368 updateView();
371 void AkonadiCollectionView::newCalendar()
373 Akonadi::AgentTypeDialog dlg( this );
374 dlg.setWindowTitle( i18n( "Add Calendar" ) );
375 dlg.agentFilterProxyModel()->addMimeTypeFilter( QString::fromLatin1( "text/calendar" ) );
376 dlg.agentFilterProxyModel()->addCapabilityFilter( "Resource" ); // show only resources, no agents
377 if ( dlg.exec() ) {
378 mNotSendAddRemoveSignal = true;
379 const Akonadi::AgentType agentType = dlg.agentType();
380 if ( agentType.isValid() ) {
381 Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob( agentType, this );
382 job->configure( this );
383 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( newCalendarDone( KJob* ) ) );
384 job->start();
389 void AkonadiCollectionView::newCalendarDone( KJob *job )
391 Akonadi::AgentInstanceCreateJob *createjob = static_cast<Akonadi::AgentInstanceCreateJob*>( job );
392 if ( createjob->error() ) {
393 //TODO(AKONADI_PORT) this should show an error dialog and should be merged with the identical code in ActionManager
394 kWarning() << "Create calendar failed:" << createjob->errorString();
395 mNotSendAddRemoveSignal = false;
396 return;
398 mNotSendAddRemoveSignal = false;
399 //TODO
402 void AkonadiCollectionView::deleteCalendar()
405 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
406 Q_ASSERT( index.isValid() );
407 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
408 Q_ASSERT( collection.isValid() );
411 const QString displayname = index.model()->data( index, Qt::DisplayRole ).toString();
412 Q_ASSERT( !displayname.isEmpty() );
414 if ( KMessageBox::warningContinueCancel( this,
415 i18n( "Do you really want to delete calendar %1?", displayname ),
416 i18n( "Delete Calendar" ),
417 KStandardGuiItem::del(),
418 KStandardGuiItem::cancel(),
419 QString(),
420 KMessageBox::Dangerous )
421 == KMessageBox::Continue ) {
423 bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
425 mNotSendAddRemoveSignal = true;
426 mWasDefaultCalendar = KOHelper::isStandardCalendar( collection.id() );
428 if ( !isTopLevel ) {
429 // deletes contents
430 Akonadi::CollectionDeleteJob *job = new Akonadi::CollectionDeleteJob( collection, this );
431 connect( job, SIGNAL( result( KJob* ) ), this, SLOT( deleteCalendarDone( KJob* ) ) );
432 } else {
433 // deletes the agent, not the contents
434 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance( collection.resource() );
435 if ( instance.isValid() ) {
436 Akonadi::AgentManager::self()->removeInstance( instance );
442 void AkonadiCollectionView::deleteCalendarDone( KJob *job )
444 Akonadi::CollectionDeleteJob *deletejob = static_cast<Akonadi::CollectionDeleteJob*>( job );
445 if ( deletejob->error() ) {
446 kWarning() << "Delete calendar failed:" << deletejob->errorString();
447 mNotSendAddRemoveSignal = false;
448 return;
450 if ( mWasDefaultCalendar ) {
451 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( Akonadi::Collection().id() );
453 mNotSendAddRemoveSignal = false;
454 //TODO
457 void AkonadiCollectionView::rowsInserted( const QModelIndex&, int, int )
459 if ( !mNotSendAddRemoveSignal )
460 emit resourcesAddedRemoved();
461 mCollectionview->expandAll();
464 #include "akonadicollectionview.moc" // for EntityModelStateSaver Q_PRIVATE_SLOT