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"