File indexing completed on 2024-05-12 05:21:19
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2003, 2004 Cornelius Schumacher <schumacher@kde.org> 0005 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0006 SPDX-FileCopyrightText: 2009 Sebastian Sauer <sebsauer@kdab.net> 0007 SPDX-FileCopyrightText: 2010-2024 Laurent Montel <montel@kde.org> 0008 SPDX-FileCopyrightText: 2012 Sérgio Martins <iamsergio@gmail.com> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0011 */ 0012 0013 #include "akonadicollectionview.h" 0014 #include "kocore.h" 0015 #include "koglobals.h" 0016 #include "kohelper.h" 0017 #include "manageshowcollectionproperties.h" 0018 #include "prefs/koprefs.h" 0019 #include "views/collectionview/calendardelegate.h" 0020 #include "views/collectionview/quickview.h" 0021 0022 #include <CalendarSupport/KCalPrefs> 0023 #include <CalendarSupport/Utils> 0024 0025 #include <Akonadi/AgentFilterProxyModel> 0026 #include <Akonadi/AgentInstanceCreateJob> 0027 #include <Akonadi/AgentManager> 0028 #include <Akonadi/AgentTypeDialog> 0029 #include <Akonadi/CalendarUtils> 0030 #include <Akonadi/CollectionDeleteJob> 0031 #include <Akonadi/CollectionFilterProxyModel> 0032 #include <Akonadi/CollectionIdentificationAttribute> 0033 #include <Akonadi/CollectionUtils> 0034 #include <Akonadi/ETMViewStateSaver> 0035 #include <Akonadi/EntityDisplayAttribute> 0036 #include <Akonadi/EntityTreeModel> 0037 #include <Akonadi/EntityTreeView> 0038 #include <Akonadi/StandardCalendarActionManager> 0039 #include <PimCommonAkonadi/MailUtil> 0040 #include <PimCommonAkonadi/ManageServerSideSubscriptionJob> 0041 0042 #include <KActionCollection> 0043 #include <KCheckableProxyModel> 0044 #include <KConfigGroup> 0045 #include <KMessageBox> 0046 0047 #include "korganizer_debug.h" 0048 #include <QSortFilterProxyModel> 0049 0050 #include <QAction> 0051 #include <QColorDialog> 0052 #include <QHeaderView> 0053 #include <QLineEdit> 0054 #include <QStackedWidget> 0055 #include <QVBoxLayout> 0056 0057 static Akonadi::EntityTreeModel *findEtm(QAbstractItemModel *model) 0058 { 0059 QAbstractProxyModel *proxyModel = nullptr; 0060 while (model) { 0061 proxyModel = qobject_cast<QAbstractProxyModel *>(model); 0062 if (proxyModel && proxyModel->sourceModel()) { 0063 model = proxyModel->sourceModel(); 0064 } else { 0065 break; 0066 } 0067 } 0068 return qobject_cast<Akonadi::EntityTreeModel *>(model); 0069 } 0070 0071 /** 0072 * Automatically checks new calendar entries 0073 */ 0074 class NewCalendarChecker : public QObject 0075 { 0076 Q_OBJECT 0077 public: 0078 NewCalendarChecker(QAbstractItemModel *model) 0079 : QObject(model) 0080 , mCheckableProxy(model) 0081 { 0082 connect(model, &QAbstractItemModel::rowsInserted, this, &NewCalendarChecker::onSourceRowsInserted); 0083 qRegisterMetaType<QPersistentModelIndex>("QPersistentModelIndex"); 0084 } 0085 0086 private Q_SLOTS: 0087 void onSourceRowsInserted(const QModelIndex &parent, int start, int end) 0088 { 0089 Akonadi::EntityTreeModel *etm = findEtm(mCheckableProxy); 0090 // Only check new collections and not during initial population 0091 if (!etm || !etm->isCollectionTreeFetched()) { 0092 return; 0093 } 0094 for (int i = start; i <= end; ++i) { 0095 qCDebug(KORGANIZER_LOG) << "checking " << i << parent << mCheckableProxy->index(i, 0, parent).data().toString(); 0096 const QModelIndex index = mCheckableProxy->index(i, 0, parent); 0097 QMetaObject::invokeMethod(this, "setCheckState", Qt::QueuedConnection, Q_ARG(QPersistentModelIndex, index)); 0098 } 0099 } 0100 0101 void setCheckState(const QPersistentModelIndex &index) 0102 { 0103 mCheckableProxy->setData(index, Qt::Checked, Qt::CheckStateRole); 0104 if (mCheckableProxy->hasChildren(index)) { 0105 onSourceRowsInserted(index, 0, mCheckableProxy->rowCount(index) - 1); 0106 } 0107 } 0108 0109 private: 0110 QAbstractItemModel *const mCheckableProxy; 0111 }; 0112 0113 /** 0114 * Handles expansion state of a treeview 0115 * 0116 * Persists state, and automatically expands new entries. 0117 * With expandAll enabled this class simply ensures that all indexes are fully expanded. 0118 */ 0119 class NewNodeExpander : public QObject 0120 { 0121 Q_OBJECT 0122 public: 0123 NewNodeExpander(QTreeView *view, bool expandAll, const QString &treeStateConfig) 0124 : QObject(view) 0125 , mTreeView(view) 0126 , mExpandAll(expandAll) 0127 , mTreeStateConfig(treeStateConfig) 0128 { 0129 connect(view->model(), &QAbstractItemModel::rowsInserted, this, &NewNodeExpander::onSourceRowsInserted); 0130 connect(view->model(), &QAbstractItemModel::layoutChanged, this, &NewNodeExpander::onLayoutChanged); 0131 connect(view->model(), &QAbstractItemModel::modelReset, this, &NewNodeExpander::onModelReset); 0132 restoreTreeState(); 0133 } 0134 0135 ~NewNodeExpander() override 0136 { 0137 // Ideally we'd automatically save the treestate of the parent view here, 0138 // but that unfortunately doesn't seem to work 0139 } 0140 0141 public Q_SLOTS: 0142 void saveState() 0143 { 0144 saveTreeState(); 0145 } 0146 0147 private Q_SLOTS: 0148 void onSourceRowsInserted(const QModelIndex &parent, int start, int end) 0149 { 0150 // The initial expansion is handled by the state saver 0151 if (!mExpandAll) { 0152 Akonadi::EntityTreeModel *etm = findEtm(mTreeView->model()); 0153 if (!etm || !etm->isCollectionTreeFetched()) { 0154 restoreTreeState(); 0155 return; 0156 } 0157 } 0158 for (int i = start; i <= end; ++i) { 0159 const QModelIndex index = mTreeView->model()->index(i, 0, parent); 0160 // qCDebug(KORGANIZER_LOG) << "expanding " << index.data().toString(); 0161 mTreeView->expand(index); 0162 if (mTreeView->model()->hasChildren(index)) { 0163 onSourceRowsInserted(index, 0, mTreeView->model()->rowCount(index) - 1); 0164 } 0165 } 0166 } 0167 0168 void onLayoutChanged() 0169 { 0170 if (mExpandAll) { 0171 onSourceRowsInserted(QModelIndex(), 0, mTreeView->model()->rowCount(QModelIndex()) - 1); 0172 } 0173 } 0174 0175 void onModelReset() 0176 { 0177 if (mExpandAll) { 0178 onSourceRowsInserted(QModelIndex(), 0, mTreeView->model()->rowCount(QModelIndex()) - 1); 0179 } 0180 } 0181 0182 private: 0183 void saveTreeState() 0184 { 0185 Akonadi::ETMViewStateSaver treeStateSaver; 0186 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0187 KConfigGroup group = config->group(mTreeStateConfig); 0188 treeStateSaver.setView(mTreeView); 0189 treeStateSaver.setSelectionModel(nullptr); // we only save expand state 0190 treeStateSaver.saveState(group); 0191 } 0192 0193 void restoreTreeState() 0194 { 0195 if (mTreeStateConfig.isEmpty()) { 0196 return; 0197 } 0198 // Otherwise ETMViewStateSaver crashes 0199 if (!findEtm(mTreeView->model())) { 0200 return; 0201 } 0202 if (treeStateRestorer) { // We don't need more than one to be running at the same time 0203 delete treeStateRestorer; 0204 } 0205 qCDebug(KORGANIZER_LOG) << "Restore tree state"; 0206 treeStateRestorer = new Akonadi::ETMViewStateSaver(); // not a leak 0207 KConfigGroup group(KSharedConfig::openConfig(), mTreeStateConfig); 0208 treeStateRestorer->setView(mTreeView); 0209 treeStateRestorer->setSelectionModel(nullptr); // we only restore expand state 0210 treeStateRestorer->restoreState(group); 0211 } 0212 0213 QPointer<Akonadi::ETMViewStateSaver> treeStateRestorer; 0214 QTreeView *mTreeView = nullptr; 0215 bool mExpandAll = false; 0216 QString mTreeStateConfig; 0217 }; 0218 0219 AkonadiCollectionViewFactory::AkonadiCollectionViewFactory(CalendarView *view) 0220 : mView(view) 0221 , mAkonadiCollectionView(nullptr) 0222 { 0223 } 0224 0225 static bool hasCompatibleMimeTypes(const Akonadi::Collection &collection) 0226 { 0227 static QStringList goodMimeTypes; 0228 0229 if (goodMimeTypes.isEmpty()) { 0230 goodMimeTypes << QStringLiteral("text/calendar") << KCalendarCore::Event::eventMimeType() << KCalendarCore::Todo::todoMimeType() 0231 << KCalendarCore::Journal::journalMimeType(); 0232 } 0233 0234 for (int i = 0; i < goodMimeTypes.count(); ++i) { 0235 if (collection.contentMimeTypes().contains(goodMimeTypes.at(i))) { 0236 return true; 0237 } 0238 } 0239 0240 return false; 0241 } 0242 0243 namespace 0244 { 0245 class ColorProxyModel : public QSortFilterProxyModel 0246 { 0247 public: 0248 explicit ColorProxyModel(QObject *parent = nullptr) 0249 : QSortFilterProxyModel(parent) 0250 , mInitDefaultCalendar(false) 0251 { 0252 } 0253 0254 QVariant data(const QModelIndex &index, int role) const override 0255 { 0256 if (!index.isValid()) { 0257 return {}; 0258 } 0259 if (role == Qt::DecorationRole) { 0260 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0261 0262 if (hasCompatibleMimeTypes(collection)) { 0263 if (collection.hasAttribute<Akonadi::EntityDisplayAttribute>() 0264 && !collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName().isEmpty()) { 0265 return collection.attribute<Akonadi::EntityDisplayAttribute>()->icon(); 0266 } 0267 } 0268 } else if (role == Qt::FontRole) { 0269 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0270 if (!collection.contentMimeTypes().isEmpty() && KOHelper::isStandardCalendar(collection.id()) 0271 && collection.rights() & Akonadi::Collection::CanCreateItem) { 0272 auto font = qvariant_cast<QFont>(QSortFilterProxyModel::data(index, Qt::FontRole)); 0273 font.setBold(true); 0274 if (!mInitDefaultCalendar) { 0275 mInitDefaultCalendar = true; 0276 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(collection.id()); 0277 } 0278 return font; 0279 } 0280 } else if (role == Qt::DisplayRole) { 0281 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0282 const Akonadi::Collection::Id colId = collection.id(); 0283 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource()); 0284 if (!instance.isOnline() && !collection.isVirtual()) { 0285 return i18nc("@item this is the default calendar", "%1 (Offline)", collection.displayName()); 0286 } 0287 if (colId == CalendarSupport::KCalPrefs::instance()->defaultCalendarId()) { 0288 return i18nc("@item this is the default calendar", "%1 (Default)", collection.displayName()); 0289 } 0290 } 0291 0292 return QSortFilterProxyModel::data(index, role); 0293 } 0294 0295 Qt::ItemFlags flags(const QModelIndex &index) const override 0296 { 0297 return Qt::ItemIsSelectable | QSortFilterProxyModel::flags(index); 0298 } 0299 0300 private: 0301 mutable bool mInitDefaultCalendar; 0302 }; 0303 0304 class CollectionFilter : public QSortFilterProxyModel 0305 { 0306 public: 0307 explicit CollectionFilter(QObject *parent = nullptr) 0308 : QSortFilterProxyModel(parent) 0309 { 0310 setDynamicSortFilter(true); 0311 } 0312 0313 protected: 0314 [[nodiscard]] bool filterAcceptsRow(int row, const QModelIndex &sourceParent) const override 0315 { 0316 const QModelIndex sourceIndex = sourceModel()->index(row, 0, sourceParent); 0317 Q_ASSERT(sourceIndex.isValid()); 0318 0319 const Akonadi::Collection &col = sourceIndex.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0320 const auto attr = col.attribute<Akonadi::CollectionIdentificationAttribute>(); 0321 0322 // We filter the user folders because we insert person nodes for user folders. 0323 if ((attr && attr->collectionNamespace().startsWith("usertoplevel")) || col.name().contains(QLatin1StringView("Other Users"))) { 0324 return false; 0325 } 0326 return true; 0327 } 0328 0329 [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override 0330 { 0331 if (role == Qt::ToolTipRole) { 0332 const Akonadi::Collection col = Akonadi::CollectionUtils::fromIndex(index); 0333 return CalendarSupport::toolTipString(col); 0334 } 0335 0336 return QSortFilterProxyModel::data(index, role); 0337 }; 0338 }; 0339 0340 class CalendarDelegateModel : public QSortFilterProxyModel 0341 { 0342 public: 0343 explicit CalendarDelegateModel(QObject *parent = nullptr) 0344 : QSortFilterProxyModel(parent) 0345 { 0346 } 0347 0348 protected: 0349 [[nodiscard]] bool checkChildren(const QModelIndex &index, int role, const QVariant &value) const 0350 { 0351 const QModelIndex sourceIndex = mapToSource(index); 0352 for (int i = 0; i < sourceModel()->rowCount(sourceIndex); ++i) { 0353 const QModelIndex child = sourceModel()->index(i, 0, sourceIndex); 0354 if (child.data(role) != value) { 0355 return false; 0356 } 0357 } 0358 return true; 0359 } 0360 0361 void setChildren(const QModelIndex &sourceIndex, const QVariant &value, int role) const 0362 { 0363 if (!sourceIndex.isValid()) { 0364 return; 0365 } 0366 for (int i = 0; i < sourceModel()->rowCount(sourceIndex); ++i) { 0367 const QModelIndex child = sourceModel()->index(i, 0, sourceIndex); 0368 sourceModel()->setData(child, value, role); 0369 setChildren(child, value, role); 0370 } 0371 } 0372 }; 0373 } 0374 0375 CalendarViewExtension *AkonadiCollectionViewFactory::create(QWidget *parent) 0376 { 0377 mAkonadiCollectionView = new AkonadiCollectionView(view(), true, parent); 0378 QObject::connect(mAkonadiCollectionView, &AkonadiCollectionView::collectionEnabled, mView, &CalendarView::collectionSelected); 0379 QObject::connect(mAkonadiCollectionView, &AkonadiCollectionView::collectionDisabled, mView, &CalendarView::collectionDeselected); 0380 QObject::connect(mAkonadiCollectionView, &AkonadiCollectionView::resourcesChanged, mView, &CalendarView::resourcesChanged); 0381 return mAkonadiCollectionView; 0382 } 0383 0384 CalendarView *AkonadiCollectionViewFactory::view() const 0385 { 0386 return mView; 0387 } 0388 0389 AkonadiCollectionView *AkonadiCollectionViewFactory::collectionView() const 0390 { 0391 return mAkonadiCollectionView; 0392 } 0393 0394 AkonadiCollectionView::AkonadiCollectionView(CalendarView *view, bool hasContextMenu, QWidget *parent) 0395 : CalendarViewExtension(parent) 0396 , mCalendarView(view) 0397 , mHasContextMenu(hasContextMenu) 0398 { 0399 mManagerShowCollectionProperties = new ManageShowCollectionProperties(this, this); 0400 0401 auto topLayout = new QVBoxLayout(this); 0402 topLayout->setContentsMargins({}); 0403 0404 auto searchCol = new QLineEdit(this); 0405 searchCol->setToolTip(i18nc("@info:tooltip", "Set search keyword")); 0406 searchCol->setWhatsThis(i18nc("@info:whatsthis", "Lets you search for a keyword in your calendars")); 0407 searchCol->setClearButtonEnabled(true); 0408 searchCol->setPlaceholderText( 0409 i18nc("@info/plain Displayed grayed-out inside the " 0410 "textbox, verb to search", 0411 "Search...")); 0412 topLayout->addWidget(searchCol); 0413 0414 auto colorProxy = new ColorProxyModel(this); 0415 colorProxy->setObjectName(QLatin1StringView("Show calendar colors")); 0416 colorProxy->setDynamicSortFilter(true); 0417 mBaseModel = colorProxy; 0418 0419 auto calendarDelegateModel = new CalendarDelegateModel(this); 0420 calendarDelegateModel->setSourceModel(mBaseModel); 0421 0422 // Hide collections that are not required 0423 auto collectionFilter = new CollectionFilter(this); 0424 collectionFilter->setSourceModel(calendarDelegateModel); 0425 0426 // Filter tree view. 0427 auto searchProxy = new ReparentingModel(this); 0428 searchProxy->setSourceModel(collectionFilter); 0429 searchProxy->setObjectName(QLatin1StringView("searchProxy")); 0430 0431 auto filterTreeViewModel = new QSortFilterProxyModel(this); 0432 filterTreeViewModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0433 filterTreeViewModel->setRecursiveFilteringEnabled(true); 0434 filterTreeViewModel->setDynamicSortFilter(true); 0435 filterTreeViewModel->setSourceModel(searchProxy); 0436 connect(searchCol, &QLineEdit::textChanged, filterTreeViewModel, &QSortFilterProxyModel::setFilterWildcard); 0437 0438 mCollectionView = new Akonadi::EntityTreeView(this); 0439 mCollectionView->header()->hide(); 0440 mCollectionView->setRootIsDecorated(true); 0441 // mCollectionView->setSorting( true ); 0442 { 0443 auto delegate = new StyledCalendarDelegate(mCollectionView); 0444 connect(delegate, &StyledCalendarDelegate::action, this, &AkonadiCollectionView::onAction); 0445 mCollectionView->setItemDelegate(delegate); 0446 } 0447 mCollectionView->setModel(filterTreeViewModel); 0448 connect(mCollectionView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AkonadiCollectionView::updateMenu); 0449 mNewNodeExpander = new NewNodeExpander(mCollectionView, false, QStringLiteral("CollectionTreeView")); 0450 0451 topLayout->addWidget(mCollectionView); 0452 0453 connect(mBaseModel, &QAbstractProxyModel::rowsInserted, this, &AkonadiCollectionView::rowsInserted); 0454 0455 KXMLGUIClient *xmlclient = KOCore::self()->xmlguiClient(view); 0456 if (xmlclient) { 0457 mCollectionView->setXmlGuiClient(xmlclient); 0458 0459 mActionManager = new Akonadi::StandardCalendarActionManager(xmlclient->actionCollection(), mCollectionView); 0460 0461 QList<Akonadi::StandardActionManager::Type> standardActions; 0462 standardActions << Akonadi::StandardActionManager::CreateCollection << Akonadi::StandardActionManager::DeleteCollections 0463 << Akonadi::StandardActionManager::SynchronizeCollections << Akonadi::StandardActionManager::CollectionProperties 0464 << Akonadi::StandardActionManager::CopyItems << Akonadi::StandardActionManager::Paste << Akonadi::StandardActionManager::DeleteItems 0465 << Akonadi::StandardActionManager::CutItems << Akonadi::StandardActionManager::CreateResource 0466 << Akonadi::StandardActionManager::DeleteResources << Akonadi::StandardActionManager::ResourceProperties 0467 << Akonadi::StandardActionManager::SynchronizeResources << Akonadi::StandardActionManager::SynchronizeCollectionTree 0468 << Akonadi::StandardActionManager::CopyCollectionToMenu << Akonadi::StandardActionManager::MoveCollectionToMenu; 0469 0470 for (Akonadi::StandardActionManager::Type standardAction : std::as_const(standardActions)) { 0471 mActionManager->createAction(standardAction); 0472 } 0473 0474 QList<Akonadi::StandardCalendarActionManager::Type> calendarActions; 0475 calendarActions << Akonadi::StandardCalendarActionManager::CreateEvent << Akonadi::StandardCalendarActionManager::CreateTodo 0476 << Akonadi::StandardCalendarActionManager::CreateSubTodo << Akonadi::StandardCalendarActionManager::CreateJournal 0477 << Akonadi::StandardCalendarActionManager::EditIncidence; 0478 0479 for (Akonadi::StandardCalendarActionManager::Type calendarAction : std::as_const(calendarActions)) { 0480 mActionManager->createAction(calendarAction); 0481 } 0482 0483 mActionManager->setCollectionSelectionModel(mCollectionView->selectionModel()); 0484 0485 mActionManager->interceptAction(Akonadi::StandardActionManager::CreateResource); 0486 mActionManager->interceptAction(Akonadi::StandardActionManager::DeleteResources); 0487 mActionManager->interceptAction(Akonadi::StandardActionManager::DeleteCollections); 0488 0489 connect(mActionManager->action(Akonadi::StandardActionManager::CreateResource), &QAction::triggered, this, &AkonadiCollectionView::newCalendar); 0490 connect(mActionManager->action(Akonadi::StandardActionManager::DeleteResources), &QAction::triggered, this, &AkonadiCollectionView::deleteCalendar); 0491 connect(mActionManager->action(Akonadi::StandardActionManager::DeleteCollections), &QAction::triggered, this, &AkonadiCollectionView::deleteCalendar); 0492 0493 mActionManager->setContextText(Akonadi::StandardActionManager::CollectionProperties, 0494 Akonadi::StandardActionManager::DialogTitle, 0495 ki18nc("@title:window", "Properties of Calendar Folder %1")); 0496 0497 mActionManager->action(Akonadi::StandardActionManager::CreateCollection) 0498 ->setProperty("ContentMimeTypes", QStringList() << Akonadi::Collection::mimeType() << KCalendarCore::Event::eventMimeType()); 0499 0500 mActionManager->interceptAction(Akonadi::StandardActionManager::CollectionProperties); 0501 connect(mActionManager->action(Akonadi::StandardActionManager::CollectionProperties), 0502 &QAction::triggered, 0503 mManagerShowCollectionProperties, 0504 &ManageShowCollectionProperties::showCollectionProperties); 0505 0506 mAssignColor = new QAction(mCollectionView); 0507 mAssignColor->setText(i18nc("@action:inmenu", "&Set Folder Color...")); 0508 mAssignColor->setEnabled(false); 0509 xmlclient->actionCollection()->addAction(QStringLiteral("assign_color"), mAssignColor); 0510 connect(mAssignColor, &QAction::triggered, this, &AkonadiCollectionView::assignColor); 0511 0512 mDefaultCalendar = new QAction(mCollectionView); 0513 mDefaultCalendar->setText(i18nc("@action:inmenu", "Set as &Default Folder")); 0514 mDefaultCalendar->setEnabled(false); 0515 xmlclient->actionCollection()->addAction(QStringLiteral("set_standard_calendar"), mDefaultCalendar); 0516 connect(mDefaultCalendar, &QAction::triggered, this, &AkonadiCollectionView::setDefaultCalendar); 0517 0518 mServerSideSubscription = 0519 new QAction(QIcon::fromTheme(QStringLiteral("folder-bookmarks")), i18nc("@action:inmenu", "Serverside Subscription..."), this); 0520 xmlclient->actionCollection()->addAction(QStringLiteral("serverside_subscription"), mServerSideSubscription); 0521 connect(mServerSideSubscription, &QAction::triggered, this, &AkonadiCollectionView::slotServerSideSubscription); 0522 } 0523 } 0524 0525 AkonadiCollectionView::~AkonadiCollectionView() 0526 { 0527 // Need this because it seems impossible to detect in the NodeExpander when to save the state 0528 // before the view is deleted. 0529 mNewNodeExpander->saveState(); 0530 } 0531 0532 void AkonadiCollectionView::slotServerSideSubscription() 0533 { 0534 const QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0535 Q_ASSERT(index.isValid()); 0536 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0537 if (!collection.isValid()) { 0538 return; 0539 } 0540 auto job = new PimCommon::ManageServerSideSubscriptionJob(this); 0541 job->setCurrentCollection(collection); 0542 job->setParentWidget(this); 0543 job->start(); 0544 } 0545 0546 Akonadi::Collection AkonadiCollectionView::currentCalendar() const 0547 { 0548 const QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0549 Q_ASSERT(index.isValid()); 0550 Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0551 return collection; 0552 } 0553 0554 void AkonadiCollectionView::setDefaultCalendar() 0555 { 0556 QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0557 Q_ASSERT(index.isValid()); 0558 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0559 0560 // Ask if they really want to do this 0561 const Akonadi::Collection curCol(CalendarSupport::KCalPrefs::instance()->defaultCalendarId()); 0562 if (curCol.isValid() 0563 && KMessageBox::warningContinueCancel(this, 0564 i18nc("@info", "Do you really want replace your current default calendar with \"%1\"?", collection.displayName()), 0565 i18nc("@title:window", "Replace Default Calendar?")) 0566 != KMessageBox::Continue) { 0567 return; 0568 } 0569 0570 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(collection.id()); 0571 CalendarSupport::KCalPrefs::instance()->usrSave(); 0572 updateMenu(); 0573 updateView(); 0574 0575 Q_EMIT defaultResourceChanged(collection); 0576 } 0577 0578 void AkonadiCollectionView::assignColor() 0579 { 0580 QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0581 Q_ASSERT(index.isValid()); 0582 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0583 Q_ASSERT(collection.isValid()); 0584 0585 const QColor defaultColor = KOHelper::resourceColor(collection); 0586 QColor myColor; 0587 myColor = QColorDialog::getColor(defaultColor); 0588 if (myColor.isValid() && myColor != defaultColor) { 0589 KOHelper::setResourceColor(collection, myColor); 0590 Q_EMIT colorsChanged(); 0591 updateMenu(); 0592 updateView(); 0593 } 0594 } 0595 0596 void AkonadiCollectionView::setCollectionSelectionProxyModel(KCheckableProxyModel *m) 0597 { 0598 if (mSelectionProxyModel == m) { 0599 return; 0600 } 0601 0602 m->selectionModel()->disconnect(this); 0603 0604 mSelectionProxyModel = m; 0605 if (!mSelectionProxyModel) { 0606 return; 0607 } 0608 0609 new NewCalendarChecker(m); 0610 mBaseModel->setSourceModel(mSelectionProxyModel); 0611 0612 connect(m->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AkonadiCollectionView::selectionChanged); 0613 } 0614 0615 KCheckableProxyModel *AkonadiCollectionView::collectionSelectionProxyModel() const 0616 { 0617 return mSelectionProxyModel; 0618 } 0619 0620 void AkonadiCollectionView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 0621 { 0622 bool changed = false; 0623 0624 for (const auto &index : selected.indexes()) { 0625 if (!index.isValid()) { 0626 continue; 0627 } 0628 0629 const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0630 if (col.isValid()) { 0631 Q_EMIT collectionEnabled(col); 0632 changed |= true; 0633 } 0634 } 0635 0636 for (const auto &index : deselected.indexes()) { 0637 if (!index.isValid()) { 0638 continue; 0639 } 0640 0641 const auto col = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0642 if (col.isValid()) { 0643 Q_EMIT collectionDisabled(col); 0644 changed |= true; 0645 } 0646 } 0647 0648 if (changed) { 0649 Q_EMIT resourcesChanged(true); 0650 } 0651 } 0652 0653 Akonadi::EntityTreeView *AkonadiCollectionView::view() const 0654 { 0655 return mCollectionView; 0656 } 0657 0658 void AkonadiCollectionView::updateView() 0659 { 0660 Q_EMIT resourcesChanged(mSelectionProxyModel ? mSelectionProxyModel->selectionModel()->hasSelection() : false); 0661 } 0662 0663 void AkonadiCollectionView::updateMenu() 0664 { 0665 if (!mHasContextMenu) { 0666 return; 0667 } 0668 bool enableAction = mCollectionView->selectionModel()->hasSelection(); 0669 enableAction = enableAction && (KOPrefs::instance()->agendaViewColors() != KOPrefs::CategoryOnly); 0670 mAssignColor->setEnabled(enableAction); 0671 QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0672 0673 bool disableStuff = true; 0674 0675 if (index.isValid()) { 0676 // Returns an invalid collection on person nodes 0677 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0678 0679 if (collection.isValid() && !collection.contentMimeTypes().isEmpty()) { 0680 if (collection.remoteId() == QLatin1StringView("akonadi_birthdays_resource")) { 0681 mAssignColor->setEnabled(false); 0682 } 0683 0684 mDefaultCalendar->setEnabled(!KOHelper::isStandardCalendar(collection.id()) && (collection.rights() & Akonadi::Collection::CanCreateItem) 0685 && !collection.isVirtual() && collection.contentMimeTypes().contains(KCalendarCore::Event::eventMimeType())); 0686 disableStuff = false; 0687 } 0688 bool isOnline; 0689 mServerSideSubscription->setEnabled(PimCommon::MailUtil::isImapFolder(collection, isOnline)); 0690 } else { 0691 mServerSideSubscription->setEnabled(false); 0692 } 0693 if (disableStuff) { 0694 mDefaultCalendar->setEnabled(false); 0695 mAssignColor->setEnabled(false); 0696 } 0697 } 0698 0699 void AkonadiCollectionView::newCalendar() 0700 { 0701 QPointer<Akonadi::AgentTypeDialog> dlg = new Akonadi::AgentTypeDialog(this); 0702 dlg->setWindowTitle(i18nc("@title:window", "Add Calendar")); 0703 dlg->agentFilterProxyModel()->addMimeTypeFilter(QStringLiteral("text/calendar")); 0704 dlg->agentFilterProxyModel()->addCapabilityFilter(QStringLiteral("Resource")); // show only resources, no agents 0705 if (dlg->exec()) { 0706 mNotSendAddRemoveSignal = true; 0707 const Akonadi::AgentType agentType = dlg->agentType(); 0708 if (agentType.isValid()) { 0709 auto job = new Akonadi::AgentInstanceCreateJob(agentType, this); 0710 job->configure(this); 0711 connect(job, &Akonadi::AgentInstanceCreateJob::result, this, &AkonadiCollectionView::newCalendarDone); 0712 job->start(); 0713 } 0714 } 0715 delete dlg; 0716 } 0717 0718 void AkonadiCollectionView::newCalendarDone(KJob *job) 0719 { 0720 auto createjob = static_cast<Akonadi::AgentInstanceCreateJob *>(job); 0721 if (createjob->error()) { 0722 // TODO(AKONADI_PORT) 0723 // this should show an error dialog and should be merged 0724 // with the identical code in ActionManager 0725 qCWarning(KORGANIZER_LOG) << "Create calendar failed:" << createjob->errorString(); 0726 mNotSendAddRemoveSignal = false; 0727 return; 0728 } 0729 mNotSendAddRemoveSignal = false; 0730 // TODO 0731 } 0732 0733 void AkonadiCollectionView::deleteCalendar() 0734 { 0735 QModelIndex index = mCollectionView->selectionModel()->currentIndex(); // selectedRows() 0736 Q_ASSERT(index.isValid()); 0737 const Akonadi::Collection collection = Akonadi::CollectionUtils::fromIndex(index); 0738 Q_ASSERT(collection.isValid()); 0739 0740 const QString displayname = index.model()->data(index, Qt::DisplayRole).toString(); 0741 Q_ASSERT(!displayname.isEmpty()); 0742 0743 if (KMessageBox::warningContinueCancel(this, 0744 i18nc("@info", "Do you really want to delete calendar %1?", displayname), 0745 i18nc("@title:window", "Delete Calendar"), 0746 KStandardGuiItem::del(), 0747 KStandardGuiItem::cancel(), 0748 QString(), 0749 KMessageBox::Dangerous) 0750 == KMessageBox::Continue) { 0751 bool isTopLevel = collection.parentCollection() == Akonadi::Collection::root(); 0752 0753 mNotSendAddRemoveSignal = true; 0754 mWasDefaultCalendar = KOHelper::isStandardCalendar(collection.id()); 0755 0756 if (!isTopLevel) { 0757 // deletes contents 0758 auto job = new Akonadi::CollectionDeleteJob(collection, this); 0759 connect(job, &Akonadi::AgentInstanceCreateJob::result, this, &AkonadiCollectionView::deleteCalendarDone); 0760 } else { 0761 // deletes the agent, not the contents 0762 const Akonadi::AgentInstance instance = Akonadi::AgentManager::self()->instance(collection.resource()); 0763 if (instance.isValid()) { 0764 Akonadi::AgentManager::self()->removeInstance(instance); 0765 } 0766 } 0767 } 0768 } 0769 0770 void AkonadiCollectionView::deleteCalendarDone(KJob *job) 0771 { 0772 auto deletejob = static_cast<Akonadi::CollectionDeleteJob *>(job); 0773 if (deletejob->error()) { 0774 qCWarning(KORGANIZER_LOG) << "Delete calendar failed:" << deletejob->errorString(); 0775 mNotSendAddRemoveSignal = false; 0776 return; 0777 } 0778 if (mWasDefaultCalendar) { 0779 CalendarSupport::KCalPrefs::instance()->setDefaultCalendarId(Akonadi::Collection().id()); 0780 } 0781 mNotSendAddRemoveSignal = false; 0782 // TODO 0783 } 0784 0785 void AkonadiCollectionView::rowsInserted(const QModelIndex &, int, int) 0786 { 0787 if (!mNotSendAddRemoveSignal) { 0788 Q_EMIT resourcesAddedRemoved(); 0789 } 0790 } 0791 0792 Akonadi::Collection AkonadiCollectionView::selectedCollection() const 0793 { 0794 Akonadi::Collection collection; 0795 QItemSelectionModel *selectionModel = mCollectionView->selectionModel(); 0796 if (!selectionModel) { 0797 return collection; 0798 } 0799 QModelIndexList indexes = selectionModel->selectedIndexes(); 0800 if (!indexes.isEmpty()) { 0801 collection = indexes.first().data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0802 } 0803 return collection; 0804 } 0805 0806 Akonadi::Collection::List AkonadiCollectionView::checkedCollections() const 0807 { 0808 Akonadi::Collection::List collections; 0809 if (!mSelectionProxyModel) { 0810 return collections; 0811 } 0812 QItemSelectionModel *selectionModel = mSelectionProxyModel->selectionModel(); 0813 if (!selectionModel) { 0814 return collections; 0815 } 0816 const QModelIndexList indexes = selectionModel->selectedIndexes(); 0817 for (const QModelIndex &index : indexes) { 0818 if (index.isValid()) { 0819 const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0820 if (collection.isValid()) { 0821 collections << collection; 0822 } 0823 } 0824 } 0825 return collections; 0826 } 0827 0828 bool AkonadiCollectionView::isChecked(const Akonadi::Collection &collection) const 0829 { 0830 if (!mSelectionProxyModel) { 0831 return false; 0832 } 0833 QItemSelectionModel *selectionModel = mSelectionProxyModel->selectionModel(); 0834 if (!selectionModel) { 0835 return false; 0836 } 0837 const QModelIndexList indexes = selectionModel->selectedIndexes(); 0838 for (const QModelIndex &index : indexes) { 0839 if (index.isValid()) { 0840 const auto c = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0841 if (c.id() == collection.id()) { 0842 return true; 0843 } 0844 } 0845 } 0846 return false; 0847 } 0848 0849 Akonadi::EntityTreeModel *AkonadiCollectionView::entityTreeModel() const 0850 { 0851 auto *etm = findEtm(mCollectionView->model()); 0852 if (!etm) { 0853 qCWarning(KORGANIZER_LOG) << "Couldn't find EntityTreeModel"; 0854 } 0855 return etm; 0856 } 0857 0858 void AkonadiCollectionView::onAction(const QModelIndex &index, int a) 0859 { 0860 const auto action = static_cast<StyledCalendarDelegate::Action>(a); 0861 switch (action) { 0862 case StyledCalendarDelegate::Quickview: { 0863 const auto collection = Akonadi::CollectionUtils::fromIndex(index); 0864 const auto title = Akonadi::CalendarUtils::displayName(entityTreeModel(), collection); 0865 auto quickview = new Quickview(mCalendarView->calendarForCollection(collection), title); 0866 quickview->setAttribute(Qt::WA_DeleteOnClose, true); 0867 quickview->show(); 0868 break; 0869 } 0870 case StyledCalendarDelegate::Total: 0871 // TODO: anything to implement here? 0872 break; 0873 } 0874 } 0875 0876 #include "akonadicollectionview.moc" 0877 0878 #include "moc_akonadicollectionview.cpp"