Pimcommon

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

KDE's Doxygen guidelines are available online.