File indexing completed on 2025-01-05 04:54:20

0001 /*
0002    SPDX-FileCopyrightText: 2013-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "knotecollectionconfigwidget.h"
0007 #include "attributes/showfoldernotesattribute.h"
0008 #include "notesharedglobalconfig.h"
0009 #include <Akonadi/ManageAccountWidget>
0010 #include <Akonadi/NoteUtils>
0011 
0012 #include <Akonadi/CollectionFilterProxyModel>
0013 #include <Akonadi/CollectionModifyJob>
0014 #include <QInputDialog>
0015 #include <QSortFilterProxyModel>
0016 
0017 #include <Akonadi/ChangeRecorder>
0018 #include <Akonadi/CollectionRequester>
0019 #include <Akonadi/EntityDisplayAttribute>
0020 #include <Akonadi/EntityTreeModel>
0021 #include <Akonadi/EntityTreeView>
0022 
0023 #include <KMime/Message>
0024 
0025 #include <KCheckableProxyModel>
0026 
0027 #include "knotes_kcm_debug.h"
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <QLineEdit>
0031 #include <QPushButton>
0032 
0033 #include <QHBoxLayout>
0034 #include <QLabel>
0035 #include <QVBoxLayout>
0036 
0037 KNoteCollectionDisplayProxyModel::KNoteCollectionDisplayProxyModel(QObject *parent)
0038     : QIdentityProxyModel(parent)
0039 {
0040 }
0041 
0042 QVariant KNoteCollectionDisplayProxyModel::data(const QModelIndex &index, int role) const
0043 {
0044     if (role == Qt::CheckStateRole) {
0045         if (index.isValid()) {
0046             const auto collection = data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0047             if (mDisplayCollection.contains(collection)) {
0048                 return mDisplayCollection.value(collection) ? Qt::Checked : Qt::Unchecked;
0049             } else {
0050                 const auto attr = collection.attribute<NoteShared::ShowFolderNotesAttribute>();
0051                 if (attr) {
0052                     return Qt::Checked;
0053                 }
0054                 return Qt::Unchecked;
0055             }
0056         }
0057     }
0058     return QIdentityProxyModel::data(index, role);
0059 }
0060 
0061 bool KNoteCollectionDisplayProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
0062 {
0063     if (role == Qt::CheckStateRole) {
0064         if (index.isValid()) {
0065             const auto collection = data(index, Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0066             mDisplayCollection[collection] = (value == Qt::Checked);
0067             Q_EMIT dataChanged(index, index);
0068             return true;
0069         }
0070     }
0071 
0072     return QIdentityProxyModel::setData(index, value, role);
0073 }
0074 
0075 Qt::ItemFlags KNoteCollectionDisplayProxyModel::flags(const QModelIndex &index) const
0076 {
0077     if (index.isValid()) {
0078         return QIdentityProxyModel::flags(index) | Qt::ItemIsUserCheckable;
0079     } else {
0080         return QIdentityProxyModel::flags(index);
0081     }
0082 }
0083 
0084 QHash<Akonadi::Collection, bool> KNoteCollectionDisplayProxyModel::displayCollection() const
0085 {
0086     return mDisplayCollection;
0087 }
0088 
0089 KNoteCollectionConfigWidget::KNoteCollectionConfigWidget(QWidget *parent)
0090     : QWidget(parent)
0091 {
0092     auto mainLayout = new QHBoxLayout(this);
0093     mainLayout->setContentsMargins(0, 0, 0, 0);
0094 
0095     auto tabWidget = new QTabWidget;
0096     mainLayout->addWidget(tabWidget);
0097 
0098     auto collectionWidget = new QWidget;
0099     auto vbox = new QVBoxLayout;
0100     collectionWidget->setLayout(vbox);
0101     tabWidget->addTab(collectionWidget, i18n("Folders"));
0102 
0103     auto label = new QLabel(i18n("Select which KNotes folders to show:"));
0104     vbox->addWidget(label);
0105 
0106     // Create a new change recorder.
0107     mChangeRecorder = new Akonadi::ChangeRecorder(this);
0108     mChangeRecorder->setMimeTypeMonitored(Akonadi::NoteUtils::noteMimeType());
0109     mChangeRecorder->fetchCollection(true);
0110     mChangeRecorder->setAllMonitored(true);
0111 
0112     mModel = new Akonadi::EntityTreeModel(mChangeRecorder, this);
0113     // Set the model to show only collections, not items.
0114     mModel->setItemPopulationStrategy(Akonadi::EntityTreeModel::NoItemPopulation);
0115 
0116     auto mimeTypeProxy = new Akonadi::CollectionFilterProxyModel(this);
0117     mimeTypeProxy->setExcludeVirtualCollections(true);
0118     mimeTypeProxy->addMimeTypeFilters(QStringList() << Akonadi::NoteUtils::noteMimeType());
0119     mimeTypeProxy->setSourceModel(mModel);
0120 
0121     mDisplayNotifierProxyModel = new KNoteCollectionDisplayProxyModel(this);
0122     mDisplayNotifierProxyModel->setSourceModel(mimeTypeProxy);
0123     connect(mDisplayNotifierProxyModel, &KNoteCollectionDisplayProxyModel::dataChanged, this, &KNoteCollectionConfigWidget::slotDataChanged);
0124 
0125     connect(mModel, &Akonadi::EntityTreeModel::collectionTreeFetched, this, &KNoteCollectionConfigWidget::slotCollectionsInserted);
0126 
0127     mCollectionFilter = new QSortFilterProxyModel(this);
0128     mCollectionFilter->setRecursiveFilteringEnabled(true);
0129     mCollectionFilter->setSourceModel(mDisplayNotifierProxyModel);
0130     mCollectionFilter->setDynamicSortFilter(true);
0131     mCollectionFilter->setFilterCaseSensitivity(Qt::CaseInsensitive);
0132 
0133     auto searchLine = new QLineEdit(this);
0134     searchLine->setPlaceholderText(i18n("Search..."));
0135     searchLine->setClearButtonEnabled(true);
0136     connect(searchLine, &QLineEdit::textChanged, this, &KNoteCollectionConfigWidget::slotSetCollectionFilter);
0137 
0138     vbox->addWidget(searchLine);
0139 
0140     mFolderView = new Akonadi::EntityTreeView(this);
0141     mFolderView->setDragEnabled(false);
0142     mFolderView->setEditTriggers(QAbstractItemView::NoEditTriggers);
0143     mFolderView->setAlternatingRowColors(true);
0144     vbox->addWidget(mFolderView);
0145 
0146     mFolderView->setModel(mCollectionFilter);
0147     connect(mFolderView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KNoteCollectionConfigWidget::slotUpdateButtons);
0148 
0149     auto hbox = new QHBoxLayout;
0150     vbox->addLayout(hbox);
0151 
0152     auto button = new QPushButton(i18n("&Select All"), this);
0153     connect(button, &QPushButton::clicked, this, &KNoteCollectionConfigWidget::slotSelectAllCollections);
0154     hbox->addWidget(button);
0155 
0156     button = new QPushButton(i18n("&Unselect All"), this);
0157     connect(button, &QPushButton::clicked, this, &KNoteCollectionConfigWidget::slotUnselectAllCollections);
0158     hbox->addWidget(button);
0159     hbox->addStretch(1);
0160 
0161     mRenameCollection = new QPushButton(i18n("Rename notes..."), this);
0162     connect(mRenameCollection, &QPushButton::clicked, this, &KNoteCollectionConfigWidget::slotRenameCollection);
0163     hbox->addWidget(mRenameCollection);
0164 
0165     vbox->addWidget(new QLabel(i18nc("@info", "Select the folder where the note will be saved:")));
0166     mDefaultSaveFolder = new Akonadi::CollectionRequester(Akonadi::Collection(NoteShared::NoteSharedGlobalConfig::self()->defaultFolder()));
0167     mDefaultSaveFolder->setMimeTypeFilter(QStringList() << Akonadi::NoteUtils::noteMimeType());
0168     mDefaultSaveFolder->setContentMimeTypes(QStringList() << QStringLiteral("application/x-vnd.akonadi.note") << QStringLiteral("text/x-vnd.akonadi.note")
0169                                                           << QStringLiteral("inode/directory"));
0170     Akonadi::CollectionDialog::CollectionDialogOptions options;
0171     options |= Akonadi::CollectionDialog::AllowToCreateNewChildCollection;
0172     options |= Akonadi::CollectionDialog::KeepTreeExpanded;
0173     mDefaultSaveFolder->changeCollectionDialogOptions(options);
0174     connect(mDefaultSaveFolder, &Akonadi::CollectionRequester::collectionChanged, this, &KNoteCollectionConfigWidget::slotDataChanged);
0175 
0176     vbox->addWidget(mDefaultSaveFolder);
0177 
0178     auto accountWidget = new QWidget;
0179     auto vboxAccountWidget = new QVBoxLayout;
0180     accountWidget->setLayout(vboxAccountWidget);
0181 
0182     auto manageAccountWidget = new Akonadi::ManageAccountWidget(this);
0183     manageAccountWidget->setDescriptionLabelText(i18n("Notes accounts"));
0184     vboxAccountWidget->addWidget(manageAccountWidget);
0185 
0186     manageAccountWidget->setMimeTypeFilter(QStringList() << Akonadi::NoteUtils::noteMimeType());
0187     manageAccountWidget->setCapabilityFilter(QStringList() << QStringLiteral("Resource")); // show only resources, no agents
0188     tabWidget->addTab(accountWidget, i18n("Accounts"));
0189     slotUpdateButtons();
0190 }
0191 
0192 KNoteCollectionConfigWidget::~KNoteCollectionConfigWidget() = default;
0193 
0194 void KNoteCollectionConfigWidget::slotUpdateButtons()
0195 {
0196     mRenameCollection->setEnabled(mFolderView->selectionModel()->hasSelection());
0197 }
0198 
0199 void KNoteCollectionConfigWidget::slotRenameCollection()
0200 {
0201     const QModelIndexList rows = mFolderView->selectionModel()->selectedRows();
0202 
0203     if (rows.size() != 1) {
0204         return;
0205     }
0206 
0207     QModelIndex idx = rows.at(0);
0208 
0209     QString title = idx.data().toString();
0210 
0211     auto col = idx.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>();
0212     Q_ASSERT(col.isValid());
0213     if (!col.isValid()) {
0214         return;
0215     }
0216 
0217     bool ok;
0218     const QString name = QInputDialog::getText(this, i18n("Rename Notes"), i18n("Name:"), QLineEdit::Normal, title, &ok);
0219 
0220     if (ok) {
0221         if (col.hasAttribute<Akonadi::EntityDisplayAttribute>() && !col.attribute<Akonadi::EntityDisplayAttribute>()->displayName().isEmpty()) {
0222             col.attribute<Akonadi::EntityDisplayAttribute>()->setDisplayName(name);
0223         } else if (!name.isEmpty()) {
0224             col.setName(name);
0225         }
0226 
0227         auto job = new Akonadi::CollectionModifyJob(col, this);
0228         connect(job, &Akonadi::CollectionModifyJob::result, this, &KNoteCollectionConfigWidget::slotCollectionModifyFinished);
0229         job->start();
0230     }
0231 }
0232 
0233 void KNoteCollectionConfigWidget::slotCollectionModifyFinished(KJob *job)
0234 {
0235     if (job->error()) {
0236         KMessageBox::error(this, i18n("An error was occurred during renaming: %1", job->errorString()), i18n("Rename note"));
0237     }
0238 }
0239 
0240 void KNoteCollectionConfigWidget::slotDataChanged()
0241 {
0242     Q_EMIT emitChanged(true);
0243 }
0244 
0245 void KNoteCollectionConfigWidget::slotSetCollectionFilter(const QString &filter)
0246 {
0247     mCollectionFilter->setFilterWildcard(filter);
0248     mFolderView->expandAll();
0249 }
0250 
0251 void KNoteCollectionConfigWidget::slotSelectAllCollections()
0252 {
0253     forceStatus(QModelIndex(), true);
0254     Q_EMIT emitChanged(true);
0255 }
0256 
0257 void KNoteCollectionConfigWidget::slotUnselectAllCollections()
0258 {
0259     forceStatus(QModelIndex(), false);
0260     Q_EMIT emitChanged(true);
0261 }
0262 
0263 void KNoteCollectionConfigWidget::forceStatus(const QModelIndex &parent, bool status)
0264 {
0265     const int nbCol = mDisplayNotifierProxyModel->rowCount(parent);
0266     for (int i = 0; i < nbCol; ++i) {
0267         const QModelIndex child = mDisplayNotifierProxyModel->index(i, 0, parent);
0268         mDisplayNotifierProxyModel->setData(child, status ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole);
0269         forceStatus(child, status);
0270     }
0271 }
0272 
0273 void KNoteCollectionConfigWidget::slotCollectionsInserted()
0274 {
0275     mFolderView->expandAll();
0276 }
0277 
0278 void KNoteCollectionConfigWidget::save()
0279 {
0280     updateCollectionsRecursive();
0281     Akonadi::Collection col = mDefaultSaveFolder->collection();
0282     if (col.isValid()) {
0283         NoteShared::NoteSharedGlobalConfig::self()->setDefaultFolder(col.id());
0284         NoteShared::NoteSharedGlobalConfig::self()->save();
0285     }
0286 }
0287 
0288 void KNoteCollectionConfigWidget::updateCollectionsRecursive()
0289 {
0290     QHashIterator<Akonadi::Collection, bool> i(mDisplayNotifierProxyModel->displayCollection());
0291     while (i.hasNext()) {
0292         i.next();
0293         Akonadi::Collection collection = i.key();
0294         Akonadi::CollectionModifyJob *modifyJob = nullptr;
0295         auto attr = collection.attribute<NoteShared::ShowFolderNotesAttribute>();
0296         const bool selected = i.value();
0297         if (selected && !attr) {
0298             attr = collection.attribute<NoteShared::ShowFolderNotesAttribute>(Akonadi::Collection::AddIfMissing);
0299             modifyJob = new Akonadi::CollectionModifyJob(collection);
0300             modifyJob->setProperty("AttributeAdded", true);
0301         } else if (!selected && attr) {
0302             collection.removeAttribute<NoteShared::ShowFolderNotesAttribute>();
0303             modifyJob = new Akonadi::CollectionModifyJob(collection);
0304             modifyJob->setProperty("AttributeAdded", false);
0305         }
0306 
0307         if (modifyJob) {
0308             connect(modifyJob, &Akonadi::CollectionModifyJob::finished, this, &KNoteCollectionConfigWidget::slotModifyJobDone);
0309         }
0310     }
0311 }
0312 
0313 void KNoteCollectionConfigWidget::slotModifyJobDone(KJob *job)
0314 {
0315     auto modifyJob = qobject_cast<Akonadi::CollectionModifyJob *>(job);
0316     if (modifyJob && job->error()) {
0317         if (job->property("AttributeAdded").toBool()) {
0318             qCWarning(KNOTES_MODULES_LOG) << "Failed to append ShowFolderNotesAttribute to collection" << modifyJob->collection().id() << ":"
0319                                           << job->errorString();
0320         } else {
0321             qCWarning(KNOTES_MODULES_LOG) << "Failed to remove ShowFolderNotesAttribute from collection" << modifyJob->collection().id() << ":"
0322                                           << job->errorString();
0323         }
0324     }
0325 }
0326 
0327 #include "moc_knotecollectionconfigwidget.cpp"