File indexing completed on 2025-01-05 04:58:21
0001 /* 0002 SPDX-FileCopyrightText: 2015-2024 Laurent Montel <montel@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 0006 */ 0007 0008 #include "completionorderwidget.h" 0009 0010 #include <KDescendantsProxyModel> 0011 #include <KLDAPWidgets/LdapClient> 0012 #include <KLDAPWidgets/LdapClientSearch> 0013 0014 #include <KContacts/Addressee> 0015 #include <KContacts/ContactGroup> 0016 #include <KLDAPWidgets/LdapClientSearchConfig> 0017 0018 #include <Akonadi/ChangeRecorder> 0019 #include <Akonadi/CollectionFilterProxyModel> 0020 #include <Akonadi/EntityTreeModel> 0021 #include <Akonadi/Monitor> 0022 0023 #include <kldapcore/ldapserver.h> 0024 0025 #include <KConfigGroup> 0026 #include <KLocalizedString> 0027 #include <QDBusConnection> 0028 #include <QHBoxLayout> 0029 #include <QPushButton> 0030 #include <QTreeWidget> 0031 #include <QTreeWidgetItem> 0032 #include <QVBoxLayout> 0033 0034 using namespace PimCommon; 0035 0036 CompletionOrderEditorAdaptor::CompletionOrderEditorAdaptor(QObject *parent) 0037 : QDBusAbstractAdaptor(parent) 0038 { 0039 setAutoRelaySignals(true); 0040 } 0041 0042 class LDAPCompletionItem : public CompletionItem 0043 { 0044 public: 0045 explicit LDAPCompletionItem(KLDAPWidgets::LdapClient *ldapClient) 0046 : mLdapClient(ldapClient) 0047 { 0048 mWeight = mLdapClient->completionWeight(); 0049 } 0050 0051 [[nodiscard]] QString label() const override 0052 { 0053 return i18n("LDAP server %1", mLdapClient->server().host()); 0054 } 0055 0056 [[nodiscard]] QIcon icon() const override 0057 { 0058 return QIcon::fromTheme(QStringLiteral("kmail")); 0059 } 0060 0061 [[nodiscard]] int completionWeight() const override 0062 { 0063 return mWeight; 0064 } 0065 0066 void save(CompletionOrderWidget *) override 0067 { 0068 KConfig *config = KLDAPWidgets::LdapClientSearchConfig::config(); 0069 KConfigGroup group(config, QStringLiteral("LDAP")); 0070 group.writeEntry(QStringLiteral("SelectedCompletionWeight%1").arg(mLdapClient->clientNumber()), mWeight); 0071 group.sync(); 0072 } 0073 0074 [[nodiscard]] bool hasEnableSupport() const override 0075 { 0076 return false; 0077 } 0078 0079 [[nodiscard]] bool isEnabled() const override 0080 { 0081 return true; 0082 } 0083 0084 void setIsEnabled(bool b) override 0085 { 0086 Q_UNUSED(b) 0087 } 0088 0089 protected: 0090 void setCompletionWeight(int weight) override 0091 { 0092 mWeight = weight; 0093 } 0094 0095 private: 0096 KLDAPWidgets::LdapClient *mLdapClient = nullptr; 0097 int mWeight; 0098 }; 0099 0100 class SimpleCompletionItem : public CompletionItem 0101 { 0102 public: 0103 SimpleCompletionItem(CompletionOrderWidget *editor, const QString &label, const QString &identifier, int weight, bool enableSupport = false) 0104 : mLabel(label) 0105 , mIdentifier(identifier) 0106 , mHasEnableSupport(enableSupport) 0107 , mEnabled(true) 0108 { 0109 KConfigGroup groupCompletionWeights(editor->configFile(), QStringLiteral("CompletionWeights")); 0110 mWeight = groupCompletionWeights.readEntry(mIdentifier, weight); 0111 if (mHasEnableSupport) { 0112 KConfigGroup groupEnabled(editor->configFile(), QStringLiteral("CompletionEnabled")); 0113 mEnabled = groupEnabled.readEntry(mIdentifier, true); 0114 } 0115 } 0116 0117 ~SimpleCompletionItem() override = default; 0118 0119 [[nodiscard]] bool isEnabled() const override 0120 { 0121 return mEnabled; 0122 } 0123 0124 [[nodiscard]] bool hasEnableSupport() const override 0125 { 0126 return mHasEnableSupport; 0127 } 0128 0129 void setIcon(const QIcon &icon) 0130 { 0131 mIcon = icon; 0132 } 0133 0134 [[nodiscard]] QString label() const override 0135 { 0136 return mLabel; 0137 } 0138 0139 [[nodiscard]] QIcon icon() const override 0140 { 0141 return mIcon; 0142 } 0143 0144 [[nodiscard]] int completionWeight() const override 0145 { 0146 return mWeight; 0147 } 0148 0149 void setIsEnabled(bool b) override 0150 { 0151 mEnabled = b; 0152 } 0153 0154 void save(CompletionOrderWidget *editor) override 0155 { 0156 KConfigGroup group(editor->configFile(), QStringLiteral("CompletionWeights")); 0157 group.writeEntry(mIdentifier, mWeight); 0158 if (mHasEnableSupport) { 0159 KConfigGroup groupEnabled(editor->configFile(), QStringLiteral("CompletionEnabled")); 0160 groupEnabled.writeEntry(mIdentifier, isEnabled()); 0161 } 0162 } 0163 0164 protected: 0165 void setCompletionWeight(int weight) override 0166 { 0167 mWeight = weight; 0168 } 0169 0170 private: 0171 QString mLabel; 0172 QString mIdentifier; 0173 int mWeight; 0174 QIcon mIcon; 0175 bool mHasEnableSupport; 0176 bool mEnabled; 0177 }; 0178 0179 ///////// 0180 0181 class CompletionViewItem : public QTreeWidgetItem 0182 { 0183 public: 0184 CompletionViewItem(QTreeWidget *parent, CompletionItem *item) 0185 : QTreeWidgetItem(parent) 0186 { 0187 setItem(item); 0188 } 0189 0190 ~CompletionViewItem() override 0191 { 0192 delete mItem; 0193 } 0194 0195 void setItem(CompletionItem *item) 0196 { 0197 mItem = item; 0198 setText(0, mItem->label()); 0199 setIcon(0, mItem->icon()); 0200 if (mItem->hasEnableSupport()) { 0201 setFlags(flags() | Qt::ItemIsUserCheckable); 0202 setCheckState(0, mItem->isEnabled() ? Qt::Checked : Qt::Unchecked); 0203 } else { 0204 setFlags(flags() & ~Qt::ItemIsUserCheckable); 0205 } 0206 } 0207 0208 [[nodiscard]] CompletionItem *item() const 0209 { 0210 return mItem; 0211 } 0212 0213 bool operator<(const QTreeWidgetItem &other) const override 0214 { 0215 const QTreeWidgetItem *otherItem = &other; 0216 const auto completionItem = static_cast<const CompletionViewItem *>(otherItem); 0217 // item with weight 100 should be on the top -> reverse sorting 0218 return mItem->completionWeight() > completionItem->item()->completionWeight(); 0219 } 0220 0221 private: 0222 CompletionItem *mItem = nullptr; 0223 }; 0224 0225 CompletionOrderWidget::CompletionOrderWidget(QWidget *parent) 0226 : QWidget(parent) 0227 , mConfig(QStringLiteral("kpimcompletionorder")) 0228 { 0229 new CompletionOrderEditorAdaptor(this); 0230 QDBusConnection::sessionBus().registerObject(QStringLiteral("/"), this, QDBusConnection::ExportAdaptors); 0231 0232 auto hbox = new QHBoxLayout(this); 0233 0234 auto page = new QWidget(this); 0235 auto pageHBoxLayout = new QHBoxLayout(page); 0236 pageHBoxLayout->setContentsMargins({}); 0237 hbox->addWidget(page); 0238 mListView = new QTreeWidget(page); 0239 mListView->setObjectName(QLatin1StringView("listview")); 0240 0241 pageHBoxLayout->addWidget(mListView); 0242 mListView->setColumnCount(1); 0243 mListView->setAlternatingRowColors(true); 0244 mListView->setIndentation(0); 0245 mListView->setAllColumnsShowFocus(true); 0246 mListView->setHeaderHidden(true); 0247 mListView->setSortingEnabled(true); 0248 0249 auto upDownBox = new QWidget(page); 0250 auto upDownBoxVBoxLayout = new QVBoxLayout(upDownBox); 0251 upDownBoxVBoxLayout->setContentsMargins({}); 0252 pageHBoxLayout->addWidget(upDownBox); 0253 mUpButton = new QPushButton(upDownBox); 0254 upDownBoxVBoxLayout->addWidget(mUpButton); 0255 mUpButton->setAutoRepeat(true); 0256 mUpButton->setObjectName(QLatin1StringView("mUpButton")); 0257 mUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 0258 mUpButton->setEnabled(false); // b/c no item is selected yet 0259 mUpButton->setToolTip(i18n("Move Up")); 0260 mUpButton->setFocusPolicy(Qt::StrongFocus); 0261 0262 mDownButton = new QPushButton(upDownBox); 0263 upDownBoxVBoxLayout->addWidget(mDownButton); 0264 mDownButton->setAutoRepeat(true); 0265 mDownButton->setObjectName(QLatin1StringView("mDownButton")); 0266 mDownButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 0267 mDownButton->setEnabled(false); // b/c no item is selected yet 0268 mDownButton->setToolTip(i18n("Move Down")); 0269 mDownButton->setFocusPolicy(Qt::StrongFocus); 0270 0271 auto spacer = new QWidget(upDownBox); 0272 upDownBoxVBoxLayout->addWidget(spacer); 0273 upDownBoxVBoxLayout->setStretchFactor(spacer, 100); 0274 0275 connect(mListView, &QTreeWidget::itemSelectionChanged, this, &CompletionOrderWidget::slotSelectionChanged); 0276 connect(mListView, &QTreeWidget::currentItemChanged, this, &CompletionOrderWidget::slotSelectionChanged); 0277 connect(mListView, &QTreeWidget::itemChanged, this, &CompletionOrderWidget::slotItemChanged); 0278 connect(mUpButton, &QAbstractButton::clicked, this, &CompletionOrderWidget::slotMoveUp); 0279 connect(mDownButton, &QAbstractButton::clicked, this, &CompletionOrderWidget::slotMoveDown); 0280 } 0281 0282 CompletionOrderWidget::~CompletionOrderWidget() = default; 0283 0284 void CompletionOrderWidget::save() 0285 { 0286 if (mDirty) { 0287 int w = 100; 0288 // Clean up order 0289 KConfigGroup group(configFile(), QStringLiteral("CompletionWeights")); 0290 group.deleteGroup(QLatin1StringView()); 0291 0292 for (int itemIndex = 0; itemIndex < mListView->topLevelItemCount(); ++itemIndex) { 0293 auto item = static_cast<CompletionViewItem *>(mListView->topLevelItem(itemIndex)); 0294 item->item()->setCompletionWeight(w); 0295 item->item()->setIsEnabled(item->checkState(0) == Qt::Checked); 0296 item->item()->save(this); 0297 --w; 0298 } 0299 Q_EMIT completionOrderChanged(); 0300 } 0301 } 0302 0303 KConfig *CompletionOrderWidget::configFile() 0304 { 0305 return &mConfig; 0306 } 0307 0308 void CompletionOrderWidget::addRecentAddressItem() 0309 { 0310 // Be default it's the first. 0311 auto item = new SimpleCompletionItem(this, i18n("Recent Addresses"), QStringLiteral("Recent Addresses"), 10); 0312 item->setIcon(QIcon::fromTheme(QStringLiteral("kmail"))); 0313 new CompletionViewItem(mListView, item); 0314 } 0315 0316 void CompletionOrderWidget::addCompletionItemForCollection(const QModelIndex &index) 0317 { 0318 const auto collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value<Akonadi::Collection>(); 0319 if (!collection.isValid()) { 0320 return; 0321 } 0322 0323 auto item = new SimpleCompletionItem(this, index.data().toString(), QString::number(collection.id()), mDefaultValue++, true); 0324 item->setIcon(index.data(Qt::DecorationRole).value<QIcon>()); 0325 0326 new CompletionViewItem(mListView, item); 0327 } 0328 0329 void CompletionOrderWidget::loadCompletionItems() 0330 { 0331 if (mLdapSearch) { 0332 // The first step is to gather all the data, creating CompletionItem objects 0333 const QList<KLDAPWidgets::LdapClient *> listClients = mLdapSearch->clients(); 0334 for (KLDAPWidgets::LdapClient *client : listClients) { 0335 new CompletionViewItem(mListView, new LDAPCompletionItem(client)); 0336 } 0337 } 0338 0339 auto monitor = new Akonadi::ChangeRecorder(this); 0340 monitor->fetchCollection(true); 0341 monitor->setCollectionMonitored(Akonadi::Collection::root()); 0342 monitor->setMimeTypeMonitored(KContacts::Addressee::mimeType(), true); 0343 monitor->setMimeTypeMonitored(KContacts::ContactGroup::mimeType(), true); 0344 0345 auto model = new Akonadi::EntityTreeModel(monitor, this); 0346 model->setItemPopulationStrategy(Akonadi::EntityTreeModel::NoItemPopulation); 0347 0348 auto descendantsProxy = new KDescendantsProxyModel(this); 0349 descendantsProxy->setDisplayAncestorData(true); 0350 descendantsProxy->setSourceModel(model); 0351 0352 auto mimeTypeProxy = new Akonadi::CollectionFilterProxyModel(this); 0353 mimeTypeProxy->addMimeTypeFilters({KContacts::Addressee::mimeType(), KContacts::ContactGroup::mimeType()}); 0354 mimeTypeProxy->setSourceModel(descendantsProxy); 0355 mimeTypeProxy->setExcludeVirtualCollections(true); 0356 0357 mCollectionModel = mimeTypeProxy; 0358 0359 connect(mimeTypeProxy, &QAbstractItemModel::rowsInserted, this, &CompletionOrderWidget::rowsInserted); 0360 for (int row = 0; row < mCollectionModel->rowCount(); ++row) { 0361 addCompletionItemForCollection(mCollectionModel->index(row, 0)); 0362 } 0363 addRecentAddressItem(); 0364 0365 mListView->sortItems(0, Qt::AscendingOrder); 0366 0367 mDirty = false; 0368 } 0369 0370 void CompletionOrderWidget::setLdapClientSearch(KLDAPWidgets::LdapClientSearch *ldapSearch) 0371 { 0372 mLdapSearch = ldapSearch; 0373 } 0374 0375 void CompletionOrderWidget::rowsInserted(const QModelIndex &parent, int start, int end) 0376 { 0377 for (int row = start; row <= end; ++row) { 0378 addCompletionItemForCollection(mCollectionModel->index(row, 0, parent)); 0379 } 0380 0381 mListView->sortItems(0, Qt::AscendingOrder); 0382 } 0383 0384 void CompletionOrderWidget::slotItemChanged() 0385 { 0386 mDirty = true; 0387 } 0388 0389 void CompletionOrderWidget::slotSelectionChanged() 0390 { 0391 QTreeWidgetItem *item = mListView->currentItem(); 0392 mDownButton->setEnabled(item && mListView->itemBelow(item)); 0393 mUpButton->setEnabled(item && mListView->itemAbove(item)); 0394 } 0395 0396 static void swapItems(CompletionViewItem *one, CompletionViewItem *other) 0397 { 0398 CompletionItem *oneCompletion = one->item(); 0399 CompletionItem *otherCompletion = other->item(); 0400 0401 int weight = otherCompletion->completionWeight(); 0402 otherCompletion->setCompletionWeight(oneCompletion->completionWeight()); 0403 oneCompletion->setCompletionWeight(weight); 0404 0405 one->setItem(oneCompletion); 0406 other->setItem(otherCompletion); 0407 } 0408 0409 void CompletionOrderWidget::slotMoveUp() 0410 { 0411 auto item = static_cast<CompletionViewItem *>(mListView->currentItem()); 0412 if (!item) { 0413 return; 0414 } 0415 auto above = static_cast<CompletionViewItem *>(mListView->itemAbove(item)); 0416 if (!above) { 0417 return; 0418 } 0419 swapItems(item, above); 0420 mListView->sortItems(0, Qt::AscendingOrder); 0421 slotSelectionChanged(); 0422 mDirty = true; 0423 } 0424 0425 void CompletionOrderWidget::slotMoveDown() 0426 { 0427 auto item = static_cast<CompletionViewItem *>(mListView->currentItem()); 0428 if (!item) { 0429 return; 0430 } 0431 auto below = static_cast<CompletionViewItem *>(mListView->itemBelow(item)); 0432 if (!below) { 0433 return; 0434 } 0435 swapItems(item, below); 0436 mListView->sortItems(0, Qt::AscendingOrder); 0437 slotSelectionChanged(); 0438 mDirty = true; 0439 } 0440 0441 #include "moc_completionorderwidget.cpp"