Better wording
[kdepim.git] / korganizer / akonadicollectionview.cpp
blob3f3e7c1f17d9222d02008fabf87d453dd79fdb5e
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 "kocore.h"
30 #include "kohelper.h"
31 #include "koprefs.h"
33 #include <calendarsupport/kcalprefs.h>
34 #include <calendarsupport/utils.h>
36 #include <Akonadi/AgentFilterProxyModel>
37 #include <Akonadi/AgentInstanceCreateJob>
38 #include <Akonadi/AgentManager>
39 #include <Akonadi/AgentTypeDialog>
40 #include <Akonadi/CollectionDeleteJob>
41 #include <Akonadi/CollectionFilterProxyModel>
42 #include <Akonadi/EntityDisplayAttribute>
43 #include <Akonadi/EntityTreeView>
44 #include <Akonadi/Calendar/StandardCalendarActionManager>
46 #include <KAction>
47 #include <KActionCollection>
48 #include <kcheckableproxymodel.h> //krazy:exclude=camelcase TODO wait for kdelibs4.8
49 #include <KColorDialog>
50 #include <KMessageBox>
51 #include <krecursivefilterproxymodel.h> //krazy:exclude=camelcase TODO wait for kdelibs4.9
53 #include <QHeaderView>
54 #include <QPainter>
55 #include <QStyledItemDelegate>
56 #include <QVBoxLayout>
58 AkonadiCollectionViewFactory::AkonadiCollectionViewFactory( CalendarView *view )
59 : mView( view ), mAkonadiCollectionView( 0 )
63 namespace {
65 static bool hasCompatibleMimeTypes( const Akonadi::Collection &collection )
67 static QStringList goodMimeTypes;
69 if ( goodMimeTypes.isEmpty() ) {
70 goodMimeTypes << QLatin1String( "text/calendar" )
71 << KCalCore::Event::eventMimeType()
72 << KCalCore::Todo::todoMimeType()
73 << KCalCore::Journal::journalMimeType();
76 for ( int i=0; i<goodMimeTypes.count(); ++i ) {
77 if ( collection.contentMimeTypes().contains( goodMimeTypes.at( i ) ) ) {
78 return true;
82 return false;
85 class ColorDelegate : public QStyledItemDelegate
87 public:
88 explicit ColorDelegate( QObject * parent = 0 ) : QStyledItemDelegate( parent )
92 void paint ( QPainter *painter, const QStyleOptionViewItem &option,
93 const QModelIndex &index ) const
95 QStyledItemDelegate::paint( painter, option, index );
96 QStyleOptionViewItemV4 v4 = option;
97 initStyleOption( &v4, index );
98 if ( v4.checkState == Qt::Checked ) {
99 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
100 QColor color = KOHelper::resourceColor( collection );
101 const bool collectionHasIcon = index.data( Qt::DecorationRole ).canConvert<QIcon>();
102 if ( color.isValid() && collectionHasIcon ) {
103 QRect r = v4.rect;
104 const int h = r.height() - 4;
105 r.adjust( r.width() - h - 2, 2, - 2, -2 );
106 painter->save();
107 painter->setRenderHint( QPainter::Antialiasing );
108 QPen pen = painter->pen();
109 pen.setColor( color );
110 QPainterPath path;
111 path.addRoundedRect( r, 5, 5 );
112 color.setAlpha( 200 );
113 painter->fillPath( path, color );
114 painter->strokePath( path, pen );
115 painter->restore();
121 class ColorProxyModel : public QSortFilterProxyModel
123 public:
124 explicit ColorProxyModel( QObject *parent=0 )
125 : QSortFilterProxyModel( parent ), mInitDefaultCalendar( false )
129 /* reimp */
130 QVariant data( const QModelIndex &index, int role ) const
132 if ( !index.isValid() ) {
133 return QVariant();
135 if ( role == Qt::DecorationRole ) {
136 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
138 if ( hasCompatibleMimeTypes( collection ) ) {
139 if ( collection.hasAttribute<Akonadi::EntityDisplayAttribute>() &&
140 !collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName().isEmpty() ) {
141 return collection.attribute<Akonadi::EntityDisplayAttribute>()->icon();
142 } else {
143 const QColor color = KOHelper::resourceColor( collection );
144 return color.isValid() ? color : QVariant();
147 } else if ( role == Qt::FontRole ) {
148 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
149 if ( !collection.contentMimeTypes().isEmpty() &&
150 KOHelper::isStandardCalendar( collection.id() ) &&
151 collection.rights() & Akonadi::Collection::CanCreateItem ) {
152 QFont font = qvariant_cast<QFont>( QSortFilterProxyModel::data( index, Qt::FontRole ) );
153 font.setBold( true );
154 if ( !mInitDefaultCalendar ) {
155 mInitDefaultCalendar = true;
156 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( collection.id() );
158 return font;
160 } else if ( role == Qt::CheckStateRole ) {
161 // Don't show the checkbox if the collection can't contain incidences
162 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
163 if ( AkonadiCollectionView::isStructuralCollection( collection ) ) {
164 return QVariant();
167 return QSortFilterProxyModel::data( index, role );
170 /* reimp */
171 Qt::ItemFlags flags( const QModelIndex &index ) const
173 return Qt::ItemIsSelectable | QSortFilterProxyModel::flags( index );
176 private:
177 mutable bool mInitDefaultCalendar;
180 } // anonymous namespace
182 CalendarViewExtension *AkonadiCollectionViewFactory::create( QWidget *parent )
184 mAkonadiCollectionView = new AkonadiCollectionView( view(), true, parent );
185 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesChanged(bool)),
186 mView, SLOT(resourcesChanged()) );
187 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesChanged(bool)),
188 mView, SLOT(updateCategories()) );
189 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesAddedRemoved()),
190 mView, SLOT(resourcesChanged()) );
191 QObject::connect( mAkonadiCollectionView, SIGNAL(resourcesAddedRemoved()),
192 mView, SLOT(updateCategories()) );
193 return mAkonadiCollectionView;
196 CalendarView *AkonadiCollectionViewFactory::view() const
198 return mView;
201 AkonadiCollectionView *AkonadiCollectionViewFactory::collectionView() const
203 return mAkonadiCollectionView;
206 AkonadiCollectionView::AkonadiCollectionView( CalendarView *view, bool hasContextMenu,
207 QWidget *parent )
208 : CalendarViewExtension( parent ),
209 mActionManager(0),
210 mCollectionview(0),
211 mBaseModel( 0 ),
212 mSelectionProxyModel( 0 ),
213 mNotSendAddRemoveSignal( false ),
214 mWasDefaultCalendar( false ),
215 mInitDefaultCalendar( false ),
216 mHasContextMenu( hasContextMenu )
218 QVBoxLayout *topLayout = new QVBoxLayout( this );
219 topLayout->setMargin( 0 );
220 topLayout->setSpacing( KDialog::spacingHint() );
222 //KLineEdit *searchCol = new KLineEdit( this );
223 //searchCol->setClearButtonShown( true );
224 //searchCol->setClickMessage( i18nc( "@info/plain Displayed grayed-out inside the "
225 // "textbox, verb to search", "Search" ) );
226 //topLayout->addWidget( searchCol );
228 Akonadi::CollectionFilterProxyModel *collectionproxymodel =
229 new Akonadi::CollectionFilterProxyModel( this );
230 collectionproxymodel->setObjectName( "Only show collections" );
231 collectionproxymodel->setDynamicSortFilter( true );
232 collectionproxymodel->addMimeTypeFilter( QString::fromLatin1( "text/calendar" ) );
233 collectionproxymodel->setExcludeVirtualCollections( true );
235 ColorProxyModel *colorProxy = new ColorProxyModel( this );
236 colorProxy->setObjectName( "Show calendar colors" );
237 colorProxy->setDynamicSortFilter( true );
238 colorProxy->setSourceModel( collectionproxymodel );
239 mBaseModel = collectionproxymodel;
241 mCollectionview = new Akonadi::EntityTreeView( this );
242 topLayout->addWidget( mCollectionview );
243 mCollectionview->header()->hide();
244 mCollectionview->setRootIsDecorated( true );
245 mCollectionview->setItemDelegate( new ColorDelegate( this ) );
247 //Filter tree view.
248 KRecursiveFilterProxyModel *filterTreeViewModel = new KRecursiveFilterProxyModel( this );
249 filterTreeViewModel->setDynamicSortFilter( true );
250 filterTreeViewModel->setSourceModel( colorProxy );
251 filterTreeViewModel->setFilterCaseSensitivity( Qt::CaseInsensitive );
252 filterTreeViewModel->setObjectName( "Recursive filtering, for the search bar" );
253 mCollectionview->setModel( filterTreeViewModel );
254 connect( mCollectionview->selectionModel(),
255 SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
256 SLOT(updateMenu()) );
258 //connect( searchCol, SIGNAL(textChanged(QString)),
259 // filterTreeViewModel, SLOT(setFilterFixedString(QString)) );
261 connect( mBaseModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
262 this, SLOT(rowsInserted(QModelIndex,int,int)) );
264 //mCollectionview->setSelectionMode( QAbstractItemView::NoSelection );
265 KXMLGUIClient *xmlclient = KOCore::self()->xmlguiClient( view );
266 if ( xmlclient ) {
267 mCollectionview->setXmlGuiClient( xmlclient );
269 mActionManager =
270 new Akonadi::StandardCalendarActionManager( xmlclient->actionCollection(), mCollectionview );
271 mActionManager->createAllActions();
272 mActionManager->setCollectionSelectionModel( mCollectionview->selectionModel() );
274 mActionManager->interceptAction( Akonadi::StandardActionManager::CreateResource );
275 mActionManager->interceptAction( Akonadi::StandardActionManager::DeleteResources );
276 mActionManager->interceptAction( Akonadi::StandardActionManager::DeleteCollections );
278 connect( mActionManager->action( Akonadi::StandardActionManager::CreateResource ),
279 SIGNAL(triggered(bool)),
280 this, SLOT(newCalendar()) );
281 connect( mActionManager->action( Akonadi::StandardActionManager::DeleteResources ),
282 SIGNAL(triggered(bool)),
283 this, SLOT(deleteCalendar()) );
284 connect( mActionManager->action( Akonadi::StandardActionManager::DeleteCollections ),
285 SIGNAL(triggered(bool)),
286 this, SLOT(deleteCalendar()) );
288 mActionManager->setContextText( Akonadi::StandardActionManager::CollectionProperties,
289 Akonadi::StandardActionManager::DialogTitle,
290 ki18nc( "@title:window", "Properties of Calendar Folder %1" ) );
292 mActionManager->action( Akonadi::StandardActionManager::CreateCollection )->
293 setProperty( "ContentMimeTypes", QStringList( KCalCore::Event::eventMimeType() ) );
295 const QStringList pages =
296 QStringList() << QLatin1String( "CalendarSupport::CollectionGeneralPage" )
297 << QLatin1String( "Akonadi::CachePolicyPage" );
299 mActionManager->setCollectionPropertiesPageNames( pages );
301 mDisableColor = new KAction( mCollectionview );
302 mDisableColor->setText( i18n( "&Disable Color" ) );
303 mDisableColor->setEnabled( false );
304 xmlclient->actionCollection()->addAction( QString::fromLatin1( "disable_color" ),
305 mDisableColor );
306 connect( mDisableColor, SIGNAL(triggered(bool)), this, SLOT(disableColor()) );
308 mAssignColor = new KAction( mCollectionview );
309 mAssignColor->setText( i18n( "&Assign Color..." ) );
310 mAssignColor->setEnabled( false );
311 xmlclient->actionCollection()->addAction( QString::fromLatin1( "assign_color" ), mAssignColor );
312 connect( mAssignColor, SIGNAL(triggered(bool)), this, SLOT(assignColor()) );
314 mDefaultCalendar = new KAction( mCollectionview );
315 mDefaultCalendar->setText( i18n( "Use as &Default Calendar" ) );
316 mDefaultCalendar->setEnabled( false );
317 xmlclient->actionCollection()->addAction( QString::fromLatin1( "set_standard_calendar" ),
318 mDefaultCalendar );
319 connect( mDefaultCalendar, SIGNAL(triggered(bool)), this, SLOT(setDefaultCalendar()) );
321 mCollectionview->expandAll();
324 AkonadiCollectionView::~AkonadiCollectionView()
328 void AkonadiCollectionView::setDefaultCalendar()
330 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
331 Q_ASSERT( index.isValid() );
332 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
333 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( collection.id() );
334 CalendarSupport::KCalPrefs::instance()->usrWriteConfig();
335 updateMenu();
336 updateView();
338 emit defaultResourceChanged( collection );
341 void AkonadiCollectionView::assignColor()
343 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
344 Q_ASSERT( index.isValid() );
345 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
346 Q_ASSERT( collection.isValid() );
348 const QString identifier = QString::number( collection.id() );
349 const QColor defaultColor = KOPrefs::instance()->resourceColor( identifier );
350 QColor myColor;
351 const int result = KColorDialog::getColor( myColor, defaultColor );
352 if ( result == KColorDialog::Accepted && myColor != defaultColor ) {
353 KOPrefs::instance()->setResourceColor( identifier, myColor );
354 emit colorsChanged();
355 updateMenu();
356 updateView();
360 void AkonadiCollectionView::disableColor()
362 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
363 Q_ASSERT( index.isValid() );
364 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
365 Q_ASSERT( collection.isValid() );
366 const QString identifier = QString::number( collection.id() );
367 KOPrefs::instance()->setResourceColor( identifier, QColor() );
368 updateMenu();
369 updateView();
370 emit colorsChanged();
373 void AkonadiCollectionView::setCollectionSelectionProxyModel( KCheckableProxyModel *m )
375 if ( mSelectionProxyModel == m ) {
376 return;
379 mSelectionProxyModel = m;
380 if ( !mSelectionProxyModel ) {
381 return;
384 mBaseModel->setSourceModel( mSelectionProxyModel );
387 KCheckableProxyModel *AkonadiCollectionView::collectionSelectionProxyModel() const
389 return mSelectionProxyModel;
392 Akonadi::EntityTreeView *AkonadiCollectionView::view() const
394 return mCollectionview;
397 void AkonadiCollectionView::updateView()
399 emit resourcesChanged( mSelectionProxyModel ?
400 mSelectionProxyModel->selectionModel()->hasSelection() :
401 false );
404 void AkonadiCollectionView::updateMenu()
406 if ( !mHasContextMenu ) {
407 return;
409 bool enableAction = mCollectionview->selectionModel()->hasSelection();
410 enableAction = enableAction &&
411 ( KOPrefs::instance()->agendaViewColors() != KOPrefs::CategoryOnly );
412 mAssignColor->setEnabled( enableAction );
413 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
415 bool disableStuff = false;
417 if ( index.isValid() ) {
418 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
419 Q_ASSERT( collection.isValid() );
421 if ( !collection.contentMimeTypes().isEmpty() ) {
422 const QString identifier = QString::number( collection.id() );
423 const QColor defaultColor = KOPrefs::instance()->resourceColor( identifier );
424 enableAction = enableAction && defaultColor.isValid();
425 mDisableColor->setEnabled( enableAction );
426 mDefaultCalendar->setEnabled( !KOHelper::isStandardCalendar( collection.id() ) &&
427 collection.rights() & Akonadi::Collection::CanCreateItem );
428 } else {
429 disableStuff = true;
431 } else {
432 disableStuff = true;
435 if ( disableStuff ) {
436 mDisableColor->setEnabled( false );
437 mDefaultCalendar->setEnabled( false );
438 mAssignColor->setEnabled( false );
442 void AkonadiCollectionView::newCalendar()
444 Akonadi::AgentTypeDialog dlg( this );
445 dlg.setWindowTitle( i18n( "Add Calendar" ) );
446 dlg.agentFilterProxyModel()->addMimeTypeFilter( QString::fromLatin1( "text/calendar" ) );
447 dlg.agentFilterProxyModel()->addCapabilityFilter( "Resource" ); // show only resources, no agents
448 if ( dlg.exec() ) {
449 mNotSendAddRemoveSignal = true;
450 const Akonadi::AgentType agentType = dlg.agentType();
451 if ( agentType.isValid() ) {
452 Akonadi::AgentInstanceCreateJob *job = new Akonadi::AgentInstanceCreateJob( agentType, this );
453 job->configure( this );
454 connect( job, SIGNAL(result(KJob*)), this, SLOT(newCalendarDone(KJob*)) );
455 job->start();
460 void AkonadiCollectionView::newCalendarDone( KJob *job )
462 Akonadi::AgentInstanceCreateJob *createjob = static_cast<Akonadi::AgentInstanceCreateJob*>( job );
463 if ( createjob->error() ) {
464 //TODO(AKONADI_PORT)
465 // this should show an error dialog and should be merged
466 // with the identical code in ActionManager
467 kWarning() << "Create calendar failed:" << createjob->errorString();
468 mNotSendAddRemoveSignal = false;
469 return;
471 mNotSendAddRemoveSignal = false;
472 //TODO
475 void AkonadiCollectionView::deleteCalendar()
477 QModelIndex index = mCollectionview->selectionModel()->currentIndex(); //selectedRows()
478 Q_ASSERT( index.isValid() );
479 const Akonadi::Collection collection = CalendarSupport::collectionFromIndex( index );
480 Q_ASSERT( collection.isValid() );
482 const QString displayname = index.model()->data( index, Qt::DisplayRole ).toString();
483 Q_ASSERT( !displayname.isEmpty() );
485 if ( KMessageBox::warningContinueCancel(
486 this,
487 i18n( "Do you really want to delete calendar %1?", displayname ),
488 i18n( "Delete Calendar" ),
489 KStandardGuiItem::del(),
490 KStandardGuiItem::cancel(),
491 QString(),
492 KMessageBox::Dangerous ) == KMessageBox::Continue ) {
494 bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root();
496 mNotSendAddRemoveSignal = true;
497 mWasDefaultCalendar = KOHelper::isStandardCalendar( collection.id() );
499 if ( !isTopLevel ) {
500 // deletes contents
501 Akonadi::CollectionDeleteJob *job = new Akonadi::CollectionDeleteJob( collection, this );
502 connect( job, SIGNAL(result(KJob*)), this, SLOT(deleteCalendarDone(KJob*)) );
503 } else {
504 // deletes the agent, not the contents
505 const Akonadi::AgentInstance instance =
506 Akonadi::AgentManager::self()->instance( collection.resource() );
507 if ( instance.isValid() ) {
508 Akonadi::AgentManager::self()->removeInstance( instance );
514 void AkonadiCollectionView::deleteCalendarDone( KJob *job )
516 Akonadi::CollectionDeleteJob *deletejob = static_cast<Akonadi::CollectionDeleteJob*>( job );
517 if ( deletejob->error() ) {
518 kWarning() << "Delete calendar failed:" << deletejob->errorString();
519 mNotSendAddRemoveSignal = false;
520 return;
522 if ( mWasDefaultCalendar ) {
523 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( Akonadi::Collection().id() );
525 mNotSendAddRemoveSignal = false;
526 //TODO
529 void AkonadiCollectionView::rowsInserted( const QModelIndex &, int, int )
531 if ( !mNotSendAddRemoveSignal ) {
532 emit resourcesAddedRemoved();
534 mCollectionview->expandAll();
537 /** static */
538 bool AkonadiCollectionView::isStructuralCollection( const Akonadi::Collection &collection )
540 QStringList mimeTypes;
541 mimeTypes << QLatin1String( "text/calendar" )
542 << KCalCore::Event::eventMimeType()
543 << KCalCore::Todo::todoMimeType()
544 << KCalCore::Journal::journalMimeType();
545 const QStringList collectionMimeTypes = collection.contentMimeTypes();
546 foreach ( const QString &mimeType, mimeTypes ) {
547 if ( collectionMimeTypes.contains( mimeType ) ) {
548 return false;
551 return true;
554 #include "akonadicollectionview.moc" // for EntityModelStateSaver Q_PRIVATE_SLOT