File indexing completed on 2024-05-12 05:12:44

0001 /*
0002     SPDX-FileCopyrightText: 2007 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "browserwidget.h"
0008 
0009 #include "akonadibrowsermodel.h"
0010 #include "collectionaclpage.h"
0011 #include "collectionattributespage.h"
0012 #include "collectioninternalspage.h"
0013 #include "config-akonadiconsole.h"
0014 #include "tagpropertiesdialog.h"
0015 #include <Akonadi/ClearCacheFoldersJob>
0016 #include <Akonadi/DbAccess>
0017 
0018 #include <Akonadi/AttributeFactory>
0019 #include <Akonadi/ChangeRecorder>
0020 #include <Akonadi/CollectionFilterProxyModel>
0021 #include <Akonadi/CollectionPropertiesDialog>
0022 #include <Akonadi/ControlGui>
0023 #include <Akonadi/EntityListView>
0024 #include <Akonadi/EntityMimeTypeFilterModel>
0025 #include <Akonadi/EntityTreeView>
0026 #include <Akonadi/FavoriteCollectionsModel>
0027 #include <Akonadi/ItemFetchJob>
0028 #include <Akonadi/ItemFetchScope>
0029 #include <Akonadi/ItemModifyJob>
0030 #include <Akonadi/Job>
0031 #include <Akonadi/SelectionProxyModel>
0032 #include <Akonadi/Session>
0033 #include <Akonadi/StandardActionManager>
0034 #include <Akonadi/StatisticsProxyModel>
0035 #include <Akonadi/TagDeleteJob>
0036 #include <Akonadi/TagFetchScope>
0037 #include <Akonadi/TagModel>
0038 #include <Akonadi/XmlWriteJob>
0039 #include <KViewStateMaintainer>
0040 #include <akonadi/private/compressionstream_p.h>
0041 
0042 #include <KCalendarCore/ICalFormat>
0043 #include <KCalendarCore/Incidence>
0044 #include <KContacts/Addressee>
0045 #include <KContacts/ContactGroup>
0046 
0047 #include "akonadiconsole_debug.h"
0048 #include <Akonadi/TagCreateJob>
0049 #include <Akonadi/TagModifyJob>
0050 #include <KActionCollection>
0051 #include <KConfig>
0052 #include <KConfigGroup>
0053 #include <KLocalizedString>
0054 #include <KMessageBox>
0055 #include <KToggleAction>
0056 #include <KXmlGuiWindow>
0057 
0058 #include <KSharedConfig>
0059 #include <QBuffer>
0060 #include <QFileDialog>
0061 #include <QMenu>
0062 #include <QSplitter>
0063 #include <QStandardItemModel>
0064 #include <QTimer>
0065 #include <QVBoxLayout>
0066 
0067 #if ENABLE_CONTENTVIEWS
0068 #include <Akonadi/ContactGroupViewer>
0069 #include <Akonadi/ContactViewer>
0070 #include <CalendarSupport/IncidenceViewer>
0071 #include <MessageViewer/Viewer>
0072 #endif
0073 
0074 using namespace Akonadi;
0075 
0076 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAttributePageFactory, CollectionAttributePage)
0077 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionInternalsPageFactory, CollectionInternalsPage)
0078 AKONADI_COLLECTION_PROPERTIES_PAGE_FACTORY(CollectionAclPageFactory, CollectionAclPage)
0079 
0080 Q_DECLARE_METATYPE(QSet<QByteArray>)
0081 
0082 BrowserWidget::BrowserWidget(KXmlGuiWindow *xmlGuiWindow, QWidget *parent)
0083     : QWidget(parent)
0084 {
0085     Q_ASSERT(xmlGuiWindow);
0086     auto layout = new QVBoxLayout(this);
0087 
0088     auto splitter = new QSplitter(Qt::Horizontal, this);
0089     splitter->setObjectName(QLatin1StringView("collectionSplitter"));
0090     layout->addWidget(splitter);
0091 
0092     auto splitter2 = new QSplitter(Qt::Vertical, this);
0093     splitter2->setObjectName(QLatin1StringView("ffvSplitter"));
0094 
0095     mCollectionView = new Akonadi::EntityTreeView(xmlGuiWindow, this);
0096     mCollectionView->setObjectName(QLatin1StringView("CollectionView"));
0097     mCollectionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0098     splitter2->addWidget(mCollectionView);
0099 
0100     auto favoritesView = new EntityListView(xmlGuiWindow, this);
0101     // favoritesView->setViewMode( QListView::IconMode );
0102     splitter2->addWidget(favoritesView);
0103 
0104     splitter->addWidget(splitter2);
0105 
0106     auto tagRecorder = new ChangeRecorder(this);
0107     tagRecorder->setObjectName(QLatin1StringView("tagRecorder"));
0108     tagRecorder->setTypeMonitored(Monitor::Tags);
0109     tagRecorder->setChangeRecordingEnabled(false);
0110     mTagView = new QTreeView(this);
0111     mTagModel = new Akonadi::TagModel(tagRecorder, this);
0112     mTagView->setModel(mTagModel);
0113     splitter2->addWidget(mTagView);
0114 
0115     mTagView->setContextMenuPolicy(Qt::CustomContextMenu);
0116     connect(mTagView, &QTreeView::customContextMenuRequested, this, &BrowserWidget::tagViewContextMenuRequested);
0117     connect(mTagView, &QTreeView::doubleClicked, this, &BrowserWidget::tagViewDoubleClicked);
0118 
0119     auto session = new Session(("AkonadiConsole Browser Widget"), this);
0120 
0121     // monitor collection changes
0122     mBrowserMonitor = new ChangeRecorder(this);
0123     mBrowserMonitor->setObjectName(QLatin1StringView("collectionMonitor"));
0124     mBrowserMonitor->setSession(session);
0125     mBrowserMonitor->setCollectionMonitored(Collection::root());
0126     mBrowserMonitor->fetchCollection(true);
0127     mBrowserMonitor->setAllMonitored(true);
0128     // TODO: Only fetch the envelope etc if possible.
0129     mBrowserMonitor->itemFetchScope().fetchFullPayload(true);
0130     mBrowserMonitor->itemFetchScope().setCacheOnly(true);
0131     mBrowserMonitor->itemFetchScope().setFetchGid(true);
0132 
0133     mBrowserModel = new AkonadiBrowserModel(mBrowserMonitor, this);
0134     mBrowserModel->setItemPopulationStrategy(EntityTreeModel::LazyPopulation);
0135     mBrowserModel->setShowSystemEntities(true);
0136     mBrowserModel->setListFilter(CollectionFetchScope::Display);
0137 
0138     //   new ModelTest( mBrowserModel );
0139 
0140     auto collectionFilter = new EntityMimeTypeFilterModel(this);
0141     collectionFilter->setSourceModel(mBrowserModel);
0142     collectionFilter->addMimeTypeInclusionFilter(Collection::mimeType());
0143     collectionFilter->setHeaderGroup(EntityTreeModel::CollectionTreeHeaders);
0144     collectionFilter->setDynamicSortFilter(true);
0145     collectionFilter->setSortCaseSensitivity(Qt::CaseInsensitive);
0146 
0147     statisticsProxyModel = new Akonadi::StatisticsProxyModel(this);
0148     statisticsProxyModel->setToolTipEnabled(true);
0149     statisticsProxyModel->setSourceModel(collectionFilter);
0150 
0151     mCollectionView->setModel(statisticsProxyModel);
0152 
0153     auto selectionProxyModel = new Akonadi::SelectionProxyModel(mCollectionView->selectionModel(), this);
0154     selectionProxyModel->setSourceModel(mBrowserModel);
0155     selectionProxyModel->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection);
0156 
0157     auto itemFilter = new EntityMimeTypeFilterModel(this);
0158     itemFilter->setSourceModel(selectionProxyModel);
0159     itemFilter->addMimeTypeExclusionFilter(Collection::mimeType());
0160     itemFilter->setHeaderGroup(EntityTreeModel::ItemListHeaders);
0161 
0162     const KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("FavoriteCollectionsModel"));
0163     connect(mBrowserModel, &AkonadiBrowserModel::columnsChanged, itemFilter, &EntityMimeTypeFilterModel::invalidate);
0164     auto sortModel = new AkonadiBrowserSortModel(mBrowserModel, this);
0165     sortModel->setDynamicSortFilter(true);
0166     sortModel->setSourceModel(itemFilter);
0167     auto favoritesModel = new FavoriteCollectionsModel(mBrowserModel, group, this);
0168     favoritesView->setModel(favoritesModel);
0169 
0170     auto splitter3 = new QSplitter(Qt::Vertical, this);
0171     splitter3->setObjectName(QLatin1StringView("itemSplitter"));
0172     splitter->addWidget(splitter3);
0173 
0174     auto itemViewParent = new QWidget(this);
0175     itemUi.setupUi(itemViewParent);
0176 
0177     itemUi.modelBox->addItem(i18n("Generic"));
0178     itemUi.modelBox->addItem(i18n("Mail"));
0179     itemUi.modelBox->addItem(i18n("Contacts"));
0180     itemUi.modelBox->addItem(i18n("Calendar/Tasks"));
0181     connect(itemUi.modelBox, static_cast<void (KComboBox::*)(int)>(&KComboBox::activated), this, &BrowserWidget::modelChanged);
0182     QTimer::singleShot(0, this, &BrowserWidget::modelChanged);
0183 
0184     itemUi.itemView->setXmlGuiClient(xmlGuiWindow);
0185     itemUi.itemView->setModel(sortModel);
0186     itemUi.itemView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0187     connect(itemUi.itemView->selectionModel(), &QItemSelectionModel::currentChanged, this, &BrowserWidget::currentChanged);
0188 
0189     splitter3->addWidget(itemViewParent);
0190     itemViewParent->layout()->setContentsMargins(0, 0, 0, 0);
0191 
0192     auto contentViewParent = new QWidget(this);
0193     contentUi.setupUi(contentViewParent);
0194     contentUi.saveButton->setEnabled(false);
0195     connect(contentUi.saveButton, &QPushButton::clicked, this, &BrowserWidget::save);
0196     splitter3->addWidget(contentViewParent);
0197 
0198 #if ENABLE_CONTENTVIEWS
0199     auto w = new QWidget;
0200     w->setLayout(new QVBoxLayout);
0201     w->layout()->addWidget(mContactView = new Akonadi::ContactViewer);
0202     contentUi.stack->addWidget(w);
0203 
0204     w = new QWidget;
0205     w->setLayout(new QVBoxLayout);
0206     w->layout()->addWidget(mContactGroupView = new Akonadi::ContactGroupViewer);
0207     contentUi.stack->addWidget(w);
0208 
0209     w = new QWidget;
0210     w->setLayout(new QVBoxLayout);
0211     w->layout()->addWidget(mIncidenceView = new CalendarSupport::IncidenceViewer);
0212     contentUi.stack->addWidget(w);
0213 
0214     w = new QWidget;
0215     w->setLayout(new QVBoxLayout);
0216     w->layout()->addWidget(mMailView = new MessageViewer::Viewer(this));
0217     contentUi.stack->addWidget(w);
0218 #endif
0219 
0220     connect(contentUi.attrAddButton, &QPushButton::clicked, this, &BrowserWidget::addAttribute);
0221     connect(contentUi.attrDeleteButton, &QPushButton::clicked, this, &BrowserWidget::delAttribute);
0222     connect(contentUi.flags, &KEditListWidget::changed, this, &BrowserWidget::contentViewChanged);
0223     connect(contentUi.tags, &KEditListWidget::changed, this, &BrowserWidget::contentViewChanged);
0224     connect(contentUi.remoteId, &QLineEdit::textChanged, this, &BrowserWidget::contentViewChanged);
0225     connect(contentUi.gid, &QLineEdit::textChanged, this, &BrowserWidget::contentViewChanged);
0226 
0227     CollectionPropertiesDialog::registerPage(new CollectionAclPageFactory());
0228     CollectionPropertiesDialog::registerPage(new CollectionAttributePageFactory());
0229     CollectionPropertiesDialog::registerPage(new CollectionInternalsPageFactory());
0230 
0231     ControlGui::widgetNeedsAkonadi(this);
0232 
0233     mStdActionManager = new StandardActionManager(xmlGuiWindow->actionCollection(), xmlGuiWindow);
0234     mStdActionManager->setCollectionSelectionModel(mCollectionView->selectionModel());
0235     mStdActionManager->setItemSelectionModel(itemUi.itemView->selectionModel());
0236     mStdActionManager->setFavoriteCollectionsModel(favoritesModel);
0237     mStdActionManager->setFavoriteSelectionModel(favoritesView->selectionModel());
0238     mStdActionManager->createAllActions();
0239 
0240     mCacheOnlyAction = new KToggleAction(i18n("Cache only retrieval"), xmlGuiWindow);
0241     mCacheOnlyAction->setChecked(true);
0242     xmlGuiWindow->actionCollection()->addAction(QStringLiteral("akonadiconsole_cacheonly"), mCacheOnlyAction);
0243     connect(mCacheOnlyAction, &KToggleAction::toggled, this, &BrowserWidget::updateItemFetchScope);
0244 
0245     m_stateMaintainer = new KViewStateMaintainer<ETMViewStateSaver>(KSharedConfig::openConfig()->group(QStringLiteral("CollectionViewState")), this);
0246     m_stateMaintainer->setView(mCollectionView);
0247 
0248     m_stateMaintainer->restoreState();
0249 }
0250 
0251 BrowserWidget::~BrowserWidget()
0252 {
0253     m_stateMaintainer->saveState();
0254 }
0255 
0256 void BrowserWidget::clear()
0257 {
0258     contentUi.stack->setCurrentWidget(contentUi.unsupportedTypePage);
0259     contentUi.dataView->clear();
0260     contentUi.id->clear();
0261     contentUi.remoteId->clear();
0262     contentUi.gid->clear();
0263     contentUi.mimeType->clear();
0264     contentUi.revision->clear();
0265     contentUi.size->clear();
0266     contentUi.modificationtime->clear();
0267     contentUi.flags->clear();
0268     contentUi.tags->clear();
0269     contentUi.attrView->setModel(nullptr);
0270 }
0271 
0272 void BrowserWidget::currentChanged(const QModelIndex &index)
0273 {
0274     const Item item = index.sibling(index.row(), 0).data(EntityTreeModel::ItemRole).value<Item>();
0275     if (!item.isValid()) {
0276         clear();
0277         return;
0278     }
0279 
0280     auto job = new ItemFetchJob(item, this);
0281     job->fetchScope().fetchFullPayload();
0282     job->fetchScope().fetchAllAttributes();
0283     job->fetchScope().setFetchTags(true);
0284     auto &tfs = job->fetchScope().tagFetchScope();
0285     tfs.setFetchIdOnly(false);
0286     tfs.fetchAllAttributes();
0287     connect(job, &ItemFetchJob::result, this, &BrowserWidget::itemFetchDone);
0288 }
0289 
0290 void BrowserWidget::itemFetchDone(KJob *job)
0291 {
0292     auto fetch = static_cast<ItemFetchJob *>(job);
0293     if (job->error()) {
0294         qCWarning(AKONADICONSOLE_LOG) << "Item fetch failed: " << job->errorString();
0295     } else if (fetch->items().isEmpty()) {
0296         qCWarning(AKONADICONSOLE_LOG) << "No item found!";
0297     } else {
0298         const Item item = fetch->items().first();
0299         setItem(item);
0300     }
0301 }
0302 
0303 void BrowserWidget::contentViewChanged()
0304 {
0305     contentUi.saveButton->setEnabled(true);
0306 }
0307 
0308 void BrowserWidget::setItem(const Akonadi::Item &item)
0309 {
0310     mCurrentItem = item;
0311 #if ENABLE_CONTENTVIEWS
0312     if (item.hasPayload<KContacts::Addressee>()) {
0313         mContactView->setItem(item);
0314         contentUi.stack->setCurrentWidget(mContactView->parentWidget());
0315     } else if (item.hasPayload<KContacts::ContactGroup>()) {
0316         mContactGroupView->setItem(item);
0317         contentUi.stack->setCurrentWidget(mContactGroupView->parentWidget());
0318     } else if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0319         mIncidenceView->setItem(item);
0320         contentUi.stack->setCurrentWidget(mIncidenceView->parentWidget());
0321     } else if (item.mimeType() == QLatin1StringView("message/rfc822") || item.mimeType() == QLatin1StringView("message/news")) {
0322         mMailView->setMessageItem(item, MimeTreeParser::Force);
0323         contentUi.stack->setCurrentWidget(mMailView->parentWidget());
0324     } else
0325 #endif
0326         if (item.hasPayload<QPixmap>()) {
0327         contentUi.imageView->setPixmap(item.payload<QPixmap>());
0328         contentUi.stack->setCurrentWidget(contentUi.imageViewPage);
0329     } else {
0330         contentUi.stack->setCurrentWidget(contentUi.unsupportedTypePage);
0331     }
0332 
0333     contentUi.saveButton->setEnabled(false);
0334 
0335     QByteArray data = item.payloadData();
0336     QBuffer buffer(&data);
0337     buffer.open(QIODevice::ReadOnly);
0338 
0339     if (Akonadi::CompressionStream::isCompressed(&buffer)) {
0340         Akonadi::CompressionStream stream(&buffer);
0341         stream.open(QIODevice::ReadOnly);
0342         data = stream.readAll();
0343     }
0344 
0345     // Note that this is true for *all* items as soon as the binary format is enabled.
0346     // Independently from how they are actually stored in the database.
0347     if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0348         quint32 magic;
0349         QDataStream input(data);
0350         input >> magic;
0351         KCalendarCore::ICalFormat format;
0352         if (magic == KCalendarCore::IncidenceBase::magicSerializationIdentifier()) {
0353             // Binary format isn't readable, show KCalendarCore string instead.
0354             auto incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0355             data = "(converted from binary format)\n" + format.toRawString(incidence);
0356         }
0357     }
0358 
0359     contentUi.dataView->setPlainText(QString::fromLatin1(data));
0360 
0361     contentUi.id->setText(QString::number(item.id()));
0362     contentUi.remoteId->setText(item.remoteId());
0363     contentUi.gid->setText(item.gid());
0364     contentUi.mimeType->setText(item.mimeType());
0365     contentUi.revision->setText(QString::number(item.revision()));
0366     contentUi.size->setText(QString::number(item.size()));
0367     contentUi.modificationtime->setText(item.modificationTime().toString() + QStringLiteral(" UTC"));
0368     QStringList flags;
0369     const auto itemFlags = item.flags();
0370     for (const Item::Flag &f : itemFlags) {
0371         flags << QString::fromUtf8(f);
0372     }
0373     contentUi.flags->setItems(flags);
0374 
0375     QStringList tags;
0376     const auto itemTags = item.tags();
0377     for (const Tag &tag : itemTags) {
0378         tags << QLatin1StringView(tag.gid());
0379     }
0380     contentUi.tags->setItems(tags);
0381 
0382     Attribute::List list = item.attributes();
0383     delete mAttrModel;
0384     mAttrModel = new QStandardItemModel();
0385     QStringList labels{QStringLiteral("Attribute"), QStringLiteral("Value")};
0386     mAttrModel->setHorizontalHeaderLabels(labels);
0387     for (const auto attr : list) {
0388         auto type = new QStandardItem(QString::fromLatin1(attr->type()));
0389         type->setEditable(false);
0390         mAttrModel->appendRow({type, new QStandardItem(QString::fromLatin1(attr->serialized()))});
0391     }
0392     contentUi.attrView->setModel(mAttrModel);
0393     connect(mAttrModel, &QStandardItemModel::itemChanged, this, &BrowserWidget::contentViewChanged);
0394 
0395     if (mMonitor) {
0396         mMonitor->deleteLater(); // might be the one calling us
0397     }
0398     mMonitor = new Monitor(this);
0399     mMonitor->setObjectName(QLatin1StringView("itemMonitor"));
0400     mMonitor->setItemMonitored(item);
0401     mMonitor->itemFetchScope().fetchFullPayload();
0402     mMonitor->itemFetchScope().fetchAllAttributes();
0403     qRegisterMetaType<QSet<QByteArray>>();
0404     connect(mMonitor, &Akonadi::Monitor::itemChanged, this, &BrowserWidget::setItem, Qt::QueuedConnection);
0405     contentUi.saveButton->setEnabled(false);
0406 }
0407 
0408 void BrowserWidget::modelChanged()
0409 {
0410     switch (itemUi.modelBox->currentIndex()) {
0411     case 1:
0412         mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::MailMode);
0413         break;
0414     case 2:
0415         mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::ContactsMode);
0416         break;
0417     case 3:
0418         mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::CalendarMode);
0419         break;
0420     default:
0421         mBrowserModel->setItemDisplayMode(AkonadiBrowserModel::GenericMode);
0422     }
0423 }
0424 
0425 void BrowserWidget::save()
0426 {
0427     Q_ASSERT(mAttrModel);
0428 
0429     const QByteArray data = contentUi.dataView->toPlainText().toUtf8();
0430     Item item = mCurrentItem;
0431     item.setRemoteId(contentUi.remoteId->text());
0432     item.setGid(contentUi.gid->text());
0433     const auto currentItemFlags = mCurrentItem.flags();
0434     for (const Item::Flag &f : currentItemFlags) {
0435         item.clearFlag(f);
0436     }
0437     const auto contentUiItemFlags = contentUi.flags->items();
0438     for (const QString &s : contentUiItemFlags) {
0439         item.setFlag(s.toUtf8());
0440     }
0441     const auto contentUiItemTags = mCurrentItem.tags();
0442     for (const Tag &tag : contentUiItemTags) {
0443         item.clearTag(tag);
0444     }
0445     const auto contentUiTagsItems = contentUi.tags->items();
0446     for (const QString &s : contentUiTagsItems) {
0447         Tag tag;
0448         tag.setGid(s.toLatin1());
0449         item.setTag(tag);
0450     }
0451     item.setPayloadFromData(data);
0452 
0453     item.clearAttributes();
0454     for (int i = 0; i < mAttrModel->rowCount(); ++i) {
0455         const QModelIndex typeIndex = mAttrModel->index(i, 0);
0456         Q_ASSERT(typeIndex.isValid());
0457         const QModelIndex valueIndex = mAttrModel->index(i, 1);
0458         Q_ASSERT(valueIndex.isValid());
0459         Attribute *attr = AttributeFactory::createAttribute(mAttrModel->data(typeIndex).toString().toLatin1());
0460         Q_ASSERT(attr);
0461         attr->deserialize(mAttrModel->data(valueIndex).toString().toLatin1());
0462         item.addAttribute(attr);
0463     }
0464 
0465     auto store = new ItemModifyJob(item, this);
0466     connect(store, &ItemModifyJob::result, this, &BrowserWidget::saveResult);
0467 }
0468 
0469 void BrowserWidget::saveResult(KJob *job)
0470 {
0471     if (job->error()) {
0472         KMessageBox::error(this, i18n("Failed to save changes: %1", job->errorString()));
0473     } else {
0474         contentUi.saveButton->setEnabled(false);
0475     }
0476 }
0477 
0478 void BrowserWidget::addAttribute()
0479 {
0480     if (!mAttrModel || contentUi.attrName->text().isEmpty()) {
0481         return;
0482     }
0483     const int row = mAttrModel->rowCount();
0484     mAttrModel->insertRow(row);
0485     QModelIndex index = mAttrModel->index(row, 0);
0486     Q_ASSERT(index.isValid());
0487     mAttrModel->setData(index, contentUi.attrName->text());
0488     contentUi.attrName->clear();
0489     contentUi.saveButton->setEnabled(true);
0490 }
0491 
0492 void BrowserWidget::delAttribute()
0493 {
0494     if (!mAttrModel) {
0495         return;
0496     }
0497     QModelIndexList selection = contentUi.attrView->selectionModel()->selectedRows();
0498     if (selection.count() != 1) {
0499         return;
0500     }
0501     mAttrModel->removeRow(selection.first().row());
0502     contentUi.saveButton->setEnabled(true);
0503 }
0504 
0505 void BrowserWidget::dumpToXml()
0506 {
0507     const Collection root = currentCollection();
0508     if (!root.isValid()) {
0509         return;
0510     }
0511     const QString fileName = QFileDialog::getSaveFileName(this, i18n("Select XML file"), QString(), QStringLiteral("*.xml"));
0512     if (fileName.isEmpty()) {
0513         return;
0514     }
0515 
0516     auto job = new XmlWriteJob(root, fileName, this);
0517     connect(job, &XmlWriteJob::result, this, &BrowserWidget::dumpToXmlResult);
0518 }
0519 
0520 void BrowserWidget::dumpToXmlResult(KJob *job)
0521 {
0522     if (job->error()) {
0523         KMessageBox::error(this, job->errorString());
0524     }
0525 }
0526 
0527 void BrowserWidget::clearCache()
0528 {
0529     auto job = new Akonadi::ClearCacheFoldersJob(currentCollection(), this);
0530     job->setParentWidget(this);
0531     job->start();
0532 }
0533 
0534 Akonadi::Collection BrowserWidget::currentCollection() const
0535 {
0536     return mCollectionView->currentIndex().data(EntityTreeModel::CollectionRole).value<Collection>();
0537 }
0538 
0539 void BrowserWidget::updateItemFetchScope()
0540 {
0541     mBrowserMonitor->itemFetchScope().setCacheOnly(mCacheOnlyAction->isChecked());
0542 }
0543 
0544 void BrowserWidget::tagViewContextMenuRequested(const QPoint &pos)
0545 {
0546     const QModelIndex index = mTagView->indexAt(pos);
0547     auto menu = new QMenu(this);
0548     connect(menu, &QMenu::aboutToHide, menu, &QMenu::deleteLater);
0549     menu->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("&Add tag..."), this, &BrowserWidget::addTagRequested);
0550     if (index.isValid()) {
0551         menu->addAction(i18n("Add &subtag..."), this, &BrowserWidget::addSubTagRequested);
0552         menu->addAction(QIcon::fromTheme(QStringLiteral("document-edit")),
0553                         i18n("&Edit tag..."),
0554                         QKeySequence(Qt::Key_Return),
0555                         this,
0556                         &BrowserWidget::editTagRequested);
0557         menu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")),
0558                         i18n("&Delete tag..."),
0559                         QKeySequence::Delete,
0560                         this,
0561                         &BrowserWidget::removeTagRequested);
0562 
0563         menu->setProperty("Tag", index.data(TagModel::TagRole));
0564     }
0565 
0566     menu->popup(mTagView->mapToGlobal(pos));
0567 }
0568 
0569 void BrowserWidget::addTagRequested()
0570 {
0571     auto dlg = new TagPropertiesDialog(this);
0572     dlg->setWindowTitle(i18nc("@title:window", "Add Tag"));
0573     connect(dlg, &TagPropertiesDialog::accepted, this, &BrowserWidget::createTag);
0574     connect(dlg, &TagPropertiesDialog::rejected, dlg, &TagPropertiesDialog::deleteLater);
0575     dlg->show();
0576 }
0577 
0578 void BrowserWidget::addSubTagRequested()
0579 {
0580     auto action = qobject_cast<QAction *>(sender());
0581     const auto parentTag = action->parent()->property("Tag").value<Akonadi::Tag>();
0582 
0583     Akonadi::Tag tag;
0584     tag.setParent(parentTag);
0585 
0586     auto dlg = new TagPropertiesDialog(tag, this);
0587     dlg->setWindowTitle(i18nc("@title:window", "Add Subtag"));
0588     connect(dlg, &TagPropertiesDialog::accepted, this, &BrowserWidget::createTag);
0589     connect(dlg, &TagPropertiesDialog::rejected, dlg, &TagPropertiesDialog::deleteLater);
0590     dlg->show();
0591 }
0592 
0593 void BrowserWidget::editTagRequested()
0594 {
0595     auto action = qobject_cast<QAction *>(sender());
0596     const auto tag = action->parent()->property("Tag").value<Akonadi::Tag>();
0597     auto dlg = new TagPropertiesDialog(tag, this);
0598     dlg->setWindowTitle(i18nc("@title:window", "Modify Tag"));
0599     connect(dlg, &TagPropertiesDialog::accepted, this, &BrowserWidget::modifyTag);
0600     connect(dlg, &TagPropertiesDialog::rejected, dlg, &TagPropertiesDialog::deleteLater);
0601     dlg->show();
0602 }
0603 
0604 void BrowserWidget::tagViewDoubleClicked(const QModelIndex &index)
0605 {
0606     if (!index.isValid()) {
0607         addTagRequested();
0608         return;
0609     }
0610 
0611     const auto tag = mTagModel->data(index, TagModel::TagRole).value<Akonadi::Tag>();
0612     Q_ASSERT(tag.isValid());
0613 
0614     auto dlg = new TagPropertiesDialog(tag, this);
0615     dlg->setWindowTitle(i18nc("@title:window", "Modify Tag"));
0616     connect(dlg, &TagPropertiesDialog::accepted, this, &BrowserWidget::modifyTag);
0617     connect(dlg, &TagPropertiesDialog::rejected, dlg, &TagPropertiesDialog::deleteLater);
0618     dlg->show();
0619 }
0620 
0621 void BrowserWidget::removeTagRequested()
0622 {
0623     if (KMessageBox::questionTwoActions(this,
0624                                         i18n("Do you really want to remove selected tag?"),
0625                                         i18n("Delete tag?"),
0626                                         KStandardGuiItem::del(),
0627                                         KStandardGuiItem::cancel(),
0628                                         QString(),
0629                                         KMessageBox::Dangerous)
0630         == KMessageBox::ButtonCode::SecondaryAction) {
0631         return;
0632     }
0633 
0634     auto action = qobject_cast<QAction *>(sender());
0635     const auto tag = action->parent()->property("Tag").value<Akonadi::Tag>();
0636     new Akonadi::TagDeleteJob(tag, this);
0637 }
0638 
0639 void BrowserWidget::createTag()
0640 {
0641     auto dlg = qobject_cast<TagPropertiesDialog *>(sender());
0642     Q_ASSERT(dlg);
0643 
0644     if (dlg->changed()) {
0645         new TagCreateJob(dlg->tag(), this);
0646     }
0647 }
0648 
0649 void BrowserWidget::modifyTag()
0650 {
0651     auto dlg = qobject_cast<TagPropertiesDialog *>(sender());
0652     Q_ASSERT(dlg);
0653 
0654     if (dlg->changed()) {
0655         new TagModifyJob(dlg->tag(), this);
0656     }
0657 }
0658 
0659 #include "moc_browserwidget.cpp"