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"
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>
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>
55 #include <QStyledItemDelegate>
56 #include <QVBoxLayout>
58 AkonadiCollectionViewFactory::AkonadiCollectionViewFactory( CalendarView
*view
)
59 : mView( view
), mAkonadiCollectionView( 0 )
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
) ) ) {
85 class ColorDelegate
: public QStyledItemDelegate
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
) {
104 const int h
= r
.height() - 4;
105 r
.adjust( r
.width() - h
- 2, 2, - 2, -2 );
107 painter
->setRenderHint( QPainter::Antialiasing
);
108 QPen pen
= painter
->pen();
109 pen
.setColor( color
);
111 path
.addRoundedRect( r
, 5, 5 );
112 color
.setAlpha( 200 );
113 painter
->fillPath( path
, color
);
114 painter
->strokePath( path
, pen
);
121 class ColorProxyModel
: public QSortFilterProxyModel
124 explicit ColorProxyModel( QObject
*parent
=0 )
125 : QSortFilterProxyModel( parent
), mInitDefaultCalendar( false )
130 QVariant
data( const QModelIndex
&index
, int role
) const
132 if ( !index
.isValid() ) {
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();
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() );
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
) ) {
167 return QSortFilterProxyModel::data( index
, role
);
171 Qt::ItemFlags
flags( const QModelIndex
&index
) const
173 return Qt::ItemIsSelectable
| QSortFilterProxyModel::flags( index
);
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
201 AkonadiCollectionView
*AkonadiCollectionViewFactory::collectionView() const
203 return mAkonadiCollectionView
;
206 AkonadiCollectionView::AkonadiCollectionView( CalendarView
*view
, bool hasContextMenu
,
208 : CalendarViewExtension( parent
),
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 ) );
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
);
267 mCollectionview
->setXmlGuiClient( xmlclient
);
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" ),
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" ),
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();
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
);
351 const int result
= KColorDialog::getColor( myColor
, defaultColor
);
352 if ( result
== KColorDialog::Accepted
&& myColor
!= defaultColor
) {
353 KOPrefs::instance()->setResourceColor( identifier
, myColor
);
354 emit
colorsChanged();
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() );
370 emit
colorsChanged();
373 void AkonadiCollectionView::setCollectionSelectionProxyModel( KCheckableProxyModel
*m
)
375 if ( mSelectionProxyModel
== m
) {
379 mSelectionProxyModel
= m
;
380 if ( !mSelectionProxyModel
) {
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() :
404 void AkonadiCollectionView::updateMenu()
406 if ( !mHasContextMenu
) {
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
);
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
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
*)) );
460 void AkonadiCollectionView::newCalendarDone( KJob
*job
)
462 Akonadi::AgentInstanceCreateJob
*createjob
= static_cast<Akonadi::AgentInstanceCreateJob
*>( job
);
463 if ( createjob
->error() ) {
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;
471 mNotSendAddRemoveSignal
= false;
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(
487 i18n( "Do you really want to delete calendar %1?", displayname
),
488 i18n( "Delete Calendar" ),
489 KStandardGuiItem::del(),
490 KStandardGuiItem::cancel(),
492 KMessageBox::Dangerous
) == KMessageBox::Continue
) {
494 bool isTopLevel
= collection
.parentCollection() == Akonadi::Collection::root();
496 mNotSendAddRemoveSignal
= true;
497 mWasDefaultCalendar
= KOHelper::isStandardCalendar( collection
.id() );
501 Akonadi::CollectionDeleteJob
*job
= new Akonadi::CollectionDeleteJob( collection
, this );
502 connect( job
, SIGNAL(result(KJob
*)), this, SLOT(deleteCalendarDone(KJob
*)) );
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;
522 if ( mWasDefaultCalendar
) {
523 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId( Akonadi::Collection().id() );
525 mNotSendAddRemoveSignal
= false;
529 void AkonadiCollectionView::rowsInserted( const QModelIndex
&, int, int )
531 if ( !mNotSendAddRemoveSignal
) {
532 emit
resourcesAddedRemoved();
534 mCollectionview
->expandAll();
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
) ) {
554 #include "akonadicollectionview.moc" // for EntityModelStateSaver Q_PRIVATE_SLOT