Pimcommon

completionorderwidget.cpp
1/*
2 SPDX-FileCopyrightText: 2015-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5
6*/
7
8#include "completionorderwidget.h"
9#include <kldapcore/ldapactivitiesabstract.h>
10
11#include <KDescendantsProxyModel>
12#include <KLDAPCore/LdapClient>
13#include <KLDAPCore/LdapClientSearch>
14
15#include <KContacts/Addressee>
16#include <KContacts/ContactGroup>
17#include <KLDAPCore/LdapClientSearchConfig>
18
19#include <Akonadi/ChangeRecorder>
20#include <Akonadi/CollectionFilterProxyModel>
21#include <Akonadi/EntityTreeModel>
22#include <Akonadi/Monitor>
23
24#include <kldapcore/ldapserver.h>
25
26#include <KConfigGroup>
27#include <KLocalizedString>
28#include <QDBusConnection>
29#include <QHBoxLayout>
30#include <QPushButton>
31#include <QTreeWidget>
32#include <QTreeWidgetItem>
33#include <QVBoxLayout>
34
35using namespace PimCommon;
36using namespace Qt::Literals::StringLiterals;
37
38CompletionOrderEditorAdaptor::CompletionOrderEditorAdaptor(QObject *parent)
39 : QDBusAbstractAdaptor(parent)
40{
41 setAutoRelaySignals(true);
42}
43
44class LDAPCompletionItem : public CompletionItem
45{
46public:
47 explicit LDAPCompletionItem(KLDAPCore::LdapClient *ldapClient)
48 : mLdapClient(ldapClient)
49 {
50 mWeight = mLdapClient->completionWeight();
51 }
52
53 [[nodiscard]] QString label() const override
54 {
55 return i18n("LDAP server %1", mLdapClient->server().host());
56 }
57
58 [[nodiscard]] QIcon icon() const override
59 {
60 return QIcon::fromTheme(QStringLiteral("kmail"));
61 }
62
63 [[nodiscard]] int completionWeight() const override
64 {
65 return mWeight;
66 }
67
68 void save(CompletionOrderWidget *) override
69 {
70 KConfig *config = KLDAPCore::LdapClientSearchConfig::config();
71 KConfigGroup group(config, QStringLiteral("LDAP"));
72 group.writeEntry(QStringLiteral("SelectedCompletionWeight%1").arg(mLdapClient->clientNumber()), mWeight);
73 group.sync();
74 }
75
76 [[nodiscard]] bool hasEnableSupport() const override
77 {
78 return false;
79 }
80
81 [[nodiscard]] bool isEnabled() const override
82 {
83 return true;
84 }
85
86 void setIsEnabled(bool b) override
87 {
88 Q_UNUSED(b)
89 }
90
91protected:
92 void setCompletionWeight(int weight) override
93 {
94 mWeight = weight;
95 }
96
97private:
98 KLDAPCore::LdapClient *const mLdapClient;
99 int mWeight;
100};
101
102class SimpleCompletionItem : public CompletionItem
103{
104public:
105 SimpleCompletionItem(CompletionOrderWidget *editor, const QString &label, const QString &identifier, int weight, bool enableSupport = false)
106 : mLabel(label)
107 , mIdentifier(identifier)
108 , mHasEnableSupport(enableSupport)
109 , mEnabled(true)
110 {
111 KConfigGroup groupCompletionWeights(editor->configFile(), QStringLiteral("CompletionWeights"));
112 mWeight = groupCompletionWeights.readEntry(mIdentifier, weight);
113 if (mHasEnableSupport) {
114 KConfigGroup groupEnabled(editor->configFile(), QStringLiteral("CompletionEnabled"));
115 mEnabled = groupEnabled.readEntry(mIdentifier, true);
116 }
117 }
118
119 ~SimpleCompletionItem() override = default;
120
121 [[nodiscard]] bool isEnabled() const override
122 {
123 return mEnabled;
124 }
125
126 [[nodiscard]] bool hasEnableSupport() const override
127 {
128 return mHasEnableSupport;
129 }
130
131 void setIcon(const QIcon &icon)
132 {
133 mIcon = icon;
134 }
135
136 [[nodiscard]] QString label() const override
137 {
138 return mLabel;
139 }
140
141 [[nodiscard]] QIcon icon() const override
142 {
143 return mIcon;
144 }
145
146 [[nodiscard]] int completionWeight() const override
147 {
148 return mWeight;
149 }
150
151 void setIsEnabled(bool b) override
152 {
153 mEnabled = b;
154 }
155
156 void save(CompletionOrderWidget *editor) override
157 {
158 KConfigGroup group(editor->configFile(), QStringLiteral("CompletionWeights"));
159 group.writeEntry(mIdentifier, mWeight);
160 if (mHasEnableSupport) {
161 KConfigGroup groupEnabled(editor->configFile(), QStringLiteral("CompletionEnabled"));
162 groupEnabled.writeEntry(mIdentifier, isEnabled());
163 }
164 }
165
166protected:
167 void setCompletionWeight(int weight) override
168 {
169 mWeight = weight;
170 }
171
172private:
173 QString mLabel;
174 QString mIdentifier;
175 int mWeight;
176 QIcon mIcon;
177 bool mHasEnableSupport;
178 bool mEnabled;
179};
180
181/////////
182
183class CompletionViewItem : public QTreeWidgetItem
184{
185public:
186 CompletionViewItem(QTreeWidget *parent, CompletionItem *item)
188 {
189 setItem(item);
190 }
191
192 ~CompletionViewItem() override
193 {
194 delete mItem;
195 }
196
197 void setItem(CompletionItem *item)
198 {
199 mItem = item;
200 setText(0, mItem->label());
201 setIcon(0, mItem->icon());
202 if (mItem->hasEnableSupport()) {
204 setCheckState(0, mItem->isEnabled() ? Qt::Checked : Qt::Unchecked);
205 } else {
207 }
208 }
209
210 [[nodiscard]] CompletionItem *item() const
211 {
212 return mItem;
213 }
214
215 bool operator<(const QTreeWidgetItem &other) const override
216 {
217 const QTreeWidgetItem *otherItem = &other;
218 const auto completionItem = static_cast<const CompletionViewItem *>(otherItem);
219 // item with weight 100 should be on the top -> reverse sorting
220 return mItem->completionWeight() > completionItem->item()->completionWeight();
221 }
222
223private:
224 CompletionItem *mItem = nullptr;
225};
226
227CompletionOrderWidget::CompletionOrderWidget(QWidget *parent)
228 : QWidget(parent)
229 , mConfig(QStringLiteral("kpimcompletionorder"))
230{
231 new CompletionOrderEditorAdaptor(this);
233
234 auto hbox = new QHBoxLayout(this);
235
236 auto page = new QWidget(this);
237 auto pageHBoxLayout = new QHBoxLayout(page);
238 pageHBoxLayout->setContentsMargins({});
239 hbox->addWidget(page);
240 mListView = new QTreeWidget(page);
241 mListView->setObjectName("listview"_L1);
242
243 pageHBoxLayout->addWidget(mListView);
244 mListView->setColumnCount(1);
245 mListView->setAlternatingRowColors(true);
246 mListView->setIndentation(0);
247 mListView->setAllColumnsShowFocus(true);
248 mListView->setHeaderHidden(true);
249 mListView->setSortingEnabled(true);
250
251 auto upDownBox = new QWidget(page);
252 auto upDownBoxVBoxLayout = new QVBoxLayout(upDownBox);
253 upDownBoxVBoxLayout->setContentsMargins({});
254 pageHBoxLayout->addWidget(upDownBox);
255 mUpButton = new QPushButton(upDownBox);
256 upDownBoxVBoxLayout->addWidget(mUpButton);
257 mUpButton->setAutoRepeat(true);
258 mUpButton->setObjectName("mUpButton"_L1);
259 mUpButton->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
260 mUpButton->setEnabled(false); // b/c no item is selected yet
261 mUpButton->setToolTip(i18nc("@info:tooltip", "Move Up"));
262 mUpButton->setFocusPolicy(Qt::StrongFocus);
263
264 mDownButton = new QPushButton(upDownBox);
265 upDownBoxVBoxLayout->addWidget(mDownButton);
266 mDownButton->setAutoRepeat(true);
267 mDownButton->setObjectName("mDownButton"_L1);
268 mDownButton->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
269 mDownButton->setEnabled(false); // b/c no item is selected yet
270 mDownButton->setToolTip(i18nc("@info:tooltip", "Move Down"));
271 mDownButton->setFocusPolicy(Qt::StrongFocus);
272
273 auto spacer = new QWidget(upDownBox);
274 upDownBoxVBoxLayout->addWidget(spacer);
275 upDownBoxVBoxLayout->setStretchFactor(spacer, 100);
276
277 connect(mListView, &QTreeWidget::itemSelectionChanged, this, &CompletionOrderWidget::slotSelectionChanged);
278 connect(mListView, &QTreeWidget::currentItemChanged, this, &CompletionOrderWidget::slotSelectionChanged);
279 connect(mListView, &QTreeWidget::itemChanged, this, &CompletionOrderWidget::slotItemChanged);
280 connect(mUpButton, &QAbstractButton::clicked, this, &CompletionOrderWidget::slotMoveUp);
281 connect(mDownButton, &QAbstractButton::clicked, this, &CompletionOrderWidget::slotMoveDown);
282}
283
284CompletionOrderWidget::~CompletionOrderWidget() = default;
285
286void CompletionOrderWidget::save()
287{
288 if (mDirty) {
289 int w = 100;
290 // Clean up order
291 KConfigGroup group(configFile(), QStringLiteral("CompletionWeights"));
292 group.deleteGroup(QLatin1StringView());
293
294 for (int itemIndex = 0; itemIndex < mListView->topLevelItemCount(); ++itemIndex) {
295 auto item = static_cast<CompletionViewItem *>(mListView->topLevelItem(itemIndex));
296 item->item()->setCompletionWeight(w);
297 item->item()->setIsEnabled(item->checkState(0) == Qt::Checked);
298 item->item()->save(this);
299 --w;
300 }
301 Q_EMIT completionOrderChanged();
302 }
303}
304
305KConfig *CompletionOrderWidget::configFile()
306{
307 return &mConfig;
308}
309
310void CompletionOrderWidget::addRecentAddressItem()
311{
312 // Be default it's the first.
313 auto item = new SimpleCompletionItem(this, i18n("Recent Addresses"), QStringLiteral("Recent Addresses"), 10);
314 item->setIcon(QIcon::fromTheme(QStringLiteral("kmail")));
315 new CompletionViewItem(mListView, item);
316}
317
318void CompletionOrderWidget::addCompletionItemForCollection(const QModelIndex &index)
319{
321 if (!collection.isValid()) {
322 return;
323 }
324
325 auto item = new SimpleCompletionItem(this, index.data().toString(), QString::number(collection.id()), mDefaultValue++, true);
326 item->setIcon(index.data(Qt::DecorationRole).value<QIcon>());
327
328 new CompletionViewItem(mListView, item);
329}
330
331void CompletionOrderWidget::loadCompletionItems()
332{
333 if (mLdapSearch) {
334 // The first step is to gather all the data, creating CompletionItem objects
335 const QList<KLDAPCore::LdapClient *> listClients = mLdapSearch->clients();
336 for (KLDAPCore::LdapClient *client : listClients) {
337 if (mLdapActivities) {
338 if (mLdapActivities->hasActivitySupport())
339 if (client->server().enablePlasmaActivities() && !client->server().activities().contains(mLdapActivities->currentActivity())) {
340 continue;
341 }
342 }
343 new CompletionViewItem(mListView, new LDAPCompletionItem(client));
344 }
345 }
346
347 auto monitor = new Akonadi::ChangeRecorder(this);
348 monitor->fetchCollection(true);
349 monitor->setCollectionMonitored(Akonadi::Collection::root());
350 monitor->setMimeTypeMonitored(KContacts::Addressee::mimeType(), true);
351 monitor->setMimeTypeMonitored(KContacts::ContactGroup::mimeType(), true);
352
353 auto model = new Akonadi::EntityTreeModel(monitor, this);
354 model->setItemPopulationStrategy(Akonadi::EntityTreeModel::NoItemPopulation);
355
356 auto descendantsProxy = new KDescendantsProxyModel(this);
357 descendantsProxy->setDisplayAncestorData(true);
358 descendantsProxy->setSourceModel(model);
359
360 auto mimeTypeProxy = new Akonadi::CollectionFilterProxyModel(this);
361 mimeTypeProxy->addMimeTypeFilters({KContacts::Addressee::mimeType(), KContacts::ContactGroup::mimeType()});
362 mimeTypeProxy->setSourceModel(descendantsProxy);
363 mimeTypeProxy->setExcludeVirtualCollections(true);
364
365 mCollectionModel = mimeTypeProxy;
366
367 connect(mimeTypeProxy, &QAbstractItemModel::rowsInserted, this, &CompletionOrderWidget::rowsInserted);
368 for (int row = 0; row < mCollectionModel->rowCount(); ++row) {
369 addCompletionItemForCollection(mCollectionModel->index(row, 0));
370 }
371 addRecentAddressItem();
372
373 mListView->sortItems(0, Qt::AscendingOrder);
374
375 mDirty = false;
376}
377
378void CompletionOrderWidget::setLdapActivitiesAbstract(KLDAPCore::LdapActivitiesAbstract *ldapActivities)
379{
380 mLdapActivities = ldapActivities;
381}
382
383void CompletionOrderWidget::setLdapClientSearch(KLDAPCore::LdapClientSearch *ldapSearch)
384{
385 mLdapSearch = ldapSearch;
386}
387
388void CompletionOrderWidget::rowsInserted(const QModelIndex &parent, int start, int end)
389{
390 for (int row = start; row <= end; ++row) {
391 addCompletionItemForCollection(mCollectionModel->index(row, 0, parent));
392 }
393
394 mListView->sortItems(0, Qt::AscendingOrder);
395}
396
397void CompletionOrderWidget::slotItemChanged()
398{
399 mDirty = true;
400}
401
402void CompletionOrderWidget::slotSelectionChanged()
403{
404 QTreeWidgetItem *item = mListView->currentItem();
405 mDownButton->setEnabled(item && mListView->itemBelow(item));
406 mUpButton->setEnabled(item && mListView->itemAbove(item));
407}
408
409static void swapItems(CompletionViewItem *one, CompletionViewItem *other)
410{
411 CompletionItem *oneCompletion = one->item();
412 CompletionItem *otherCompletion = other->item();
413
414 int weight = otherCompletion->completionWeight();
415 otherCompletion->setCompletionWeight(oneCompletion->completionWeight());
416 oneCompletion->setCompletionWeight(weight);
417
418 one->setItem(oneCompletion);
419 other->setItem(otherCompletion);
420}
421
422void CompletionOrderWidget::slotMoveUp()
423{
424 auto item = static_cast<CompletionViewItem *>(mListView->currentItem());
425 if (!item) {
426 return;
427 }
428 auto above = static_cast<CompletionViewItem *>(mListView->itemAbove(item));
429 if (!above) {
430 return;
431 }
432 swapItems(item, above);
433 mListView->sortItems(0, Qt::AscendingOrder);
434 slotSelectionChanged();
435 mDirty = true;
436}
437
438void CompletionOrderWidget::slotMoveDown()
439{
440 auto item = static_cast<CompletionViewItem *>(mListView->currentItem());
441 if (!item) {
442 return;
443 }
444 auto below = static_cast<CompletionViewItem *>(mListView->itemBelow(item));
445 if (!below) {
446 return;
447 }
448 swapItems(item, below);
449 mListView->sortItems(0, Qt::AscendingOrder);
450 slotSelectionChanged();
451 mDirty = true;
452}
453
454#include "moc_completionorderwidget.cpp"
static Collection root()
static QString mimeType()
static QString mimeType()
QList< KLDAPCore::LdapClient * > clients() const
int completionWeight() const
const KLDAPCore::LdapServer server() const
int clientNumber() const
QString host() const
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
QString label(StandardShortcut id)
const QList< QKeySequence > & end()
folderdialogacltab.h
void clicked(bool checked)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
void rowsInserted(const QModelIndex &parent, int first, int last)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
QIcon fromTheme(const QString &name)
QVariant data(int role) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString number(double n, char format, int precision)
StrongFocus
DecorationRole
ItemIsUserCheckable
AscendingOrder
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QTreeWidgetItem * currentItem() const const
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
QTreeWidgetItem * itemAbove(const QTreeWidgetItem *item) const const
QTreeWidgetItem * itemBelow(const QTreeWidgetItem *item) const const
void itemChanged(QTreeWidgetItem *item, int column)
void itemSelectionChanged()
void sortItems(int column, Qt::SortOrder order)
QTreeWidgetItem * topLevelItem(int index) const const
Qt::ItemFlags flags() const const
QTreeWidgetItem * parent() const const
void setCheckState(int column, Qt::CheckState state)
void setFlags(Qt::ItemFlags flags)
void setIcon(int column, const QIcon &icon)
void setText(int column, const QString &text)
QString toString() const const
T value() const const
void setEnabled(bool)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 11:57:39 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.