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

KDE's Doxygen guidelines are available online.