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"