Akonadi

tageditwidget.cpp
1/*
2 This file is part of Akonadi
3
4 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8#include "tageditwidget.h"
9#include "changerecorder.h"
10#include "tagattribute.h"
11#include "tagcreatejob.h"
12#include "tagdeletejob.h"
13#include "tagfetchscope.h"
14#include "tagmodel.h"
15#include "ui_tageditwidget.h"
16
17#include <KCheckableProxyModel>
18#include <KLocalizedString>
19#include <KMessageBox>
20
21#include <QEvent>
22#include <QPushButton>
23
24using namespace Akonadi;
25
26class Akonadi::TagEditWidgetPrivate : public QObject
27{
29public:
30 explicit TagEditWidgetPrivate(QWidget *parent);
31
32public Q_SLOTS:
33 void slotTextEdited(const QString &text);
34 void slotItemEntered(const QModelIndex &index);
35 void deleteTag();
36 void slotCreateTag();
37 void slotCreateTagFinished(KJob *job);
38 void onRowsInserted(const QModelIndex &parent, int start, int end);
39 void onModelPopulated();
40
41public:
42 void initCheckableProxy(Akonadi::TagModel *model)
43 {
44 Q_ASSERT(m_checkableProxy);
45
46 auto selectionModel = new QItemSelectionModel(model, m_checkableProxy.get());
47 m_checkableProxy->setSourceModel(model);
48 m_checkableProxy->setSelectionModel(selectionModel);
49 }
50
51 void select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const;
52 enum ItemType {
53 UrlTag = Qt::UserRole + 1,
54 };
55
56 QWidget *const d;
57 Ui::TagEditWidget ui;
58
59 Akonadi::Tag::List m_tags;
60 Akonadi::TagModel *m_model = nullptr;
62 QModelIndex m_deleteCandidate;
63
64 QPushButton *m_deleteButton = nullptr;
65};
66
67TagEditWidgetPrivate::TagEditWidgetPrivate(QWidget *parent)
68 : d(parent)
69{
70}
71
72void TagEditWidgetPrivate::select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const
73{
74 if (!m_model) {
75 return;
76 }
77
78 QItemSelection selection;
79 for (int i = start; i <= end; i++) {
80 const QModelIndex index = m_model->index(i, 0, parent);
81 const auto insertedTag = index.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>();
82 if (m_tags.contains(insertedTag)) {
83 selection.select(index, index);
84 }
85 }
86 if (m_checkableProxy) {
87 m_checkableProxy->selectionModel()->select(selection, selectionFlag);
88 }
89}
90
91void TagEditWidgetPrivate::onModelPopulated()
92{
93 select(QModelIndex(), 0, m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
94}
95
96void TagEditWidgetPrivate::onRowsInserted(const QModelIndex &parent, int start, int end)
97{
99}
100
101void TagEditWidgetPrivate::slotCreateTag()
102{
103 if (ui.newTagButton->isEnabled()) {
104 auto createJob = new TagCreateJob(Akonadi::Tag(ui.newTagEdit->text()), this);
105 connect(createJob, &TagCreateJob::finished, this, &TagEditWidgetPrivate::slotCreateTagFinished);
106
107 ui.newTagEdit->clear();
108 ui.newTagEdit->setEnabled(false);
109 ui.newTagButton->setEnabled(false);
110 }
111}
112
113void TagEditWidgetPrivate::slotCreateTagFinished(KJob *job)
114{
115 if (job->error()) {
116 KMessageBox::error(d, i18n("Failed to create a new tag"), i18nc("@title:window", "An Error Occurred while Creating a New Tag"));
117 }
118
119 ui.newTagEdit->setEnabled(true);
120}
121
122void TagEditWidgetPrivate::slotTextEdited(const QString &text)
123{
124 // Remove unnecessary spaces from a new tag is
125 // mandatory, as the user cannot see the difference
126 // between a tag "Test" and "Test ".
127 const QString tagText = text.simplified();
128 if (tagText.isEmpty()) {
129 ui.newTagButton->setEnabled(false);
130 return;
131 }
132
133 // Check whether the new tag already exists
134 bool exists = false;
135 for (int i = 0, count = m_model->rowCount(); i < count; ++i) {
136 const QModelIndex index = m_model->index(i, 0, QModelIndex());
137 if (index.data(Qt::DisplayRole).toString() == tagText) {
138 exists = true;
139 break;
140 }
141 }
142 ui.newTagButton->setEnabled(!exists);
143}
144
145void TagEditWidgetPrivate::slotItemEntered(const QModelIndex &index)
146{
147 // align the delete-button to stay on the right border
148 // of the item
149 const QRect rect = ui.tagsView->visualRect(index);
150 const int size = rect.height();
151 const int x = rect.right() - size;
152 const int y = rect.top();
153 m_deleteButton->move(x, y);
154 m_deleteButton->resize(size, size);
155
156 m_deleteCandidate = index;
157 m_deleteButton->show();
158}
159
160void TagEditWidgetPrivate::deleteTag()
161{
162 Q_ASSERT(m_deleteCandidate.isValid());
163 const auto tag = m_deleteCandidate.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>();
164 const QString text = xi18nc("@info", "Do you really want to remove the tag <resource>%1</resource>?", tag.name());
165 const QString caption = i18nc("@title:window", "Delete Tag");
166 if (KMessageBox::questionTwoActions(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::ButtonCode::PrimaryAction) {
167 new TagDeleteJob(tag, this);
168 }
169}
170
171TagEditWidget::TagEditWidget(QWidget *parent)
172 : QWidget(parent)
173 , d(new TagEditWidgetPrivate(this))
174{
175 d->ui.setupUi(this);
176
177 d->ui.tagsView->installEventFilter(this);
178 connect(d->ui.tagsView, &QAbstractItemView::entered, d.get(), &TagEditWidgetPrivate::slotItemEntered);
179
180 connect(d->ui.newTagEdit, &QLineEdit::textEdited, d.get(), &TagEditWidgetPrivate::slotTextEdited);
181 connect(d->ui.newTagEdit, &QLineEdit::returnPressed, d.get(), &TagEditWidgetPrivate::slotCreateTag);
182 connect(d->ui.newTagButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::slotCreateTag);
183
184 // create the delete button, which is shown when
185 // hovering the items
186 d->m_deleteButton = new QPushButton(d->ui.tagsView->viewport());
187 d->m_deleteButton->setObjectName(QLatin1StringView("tagDeleteButton"));
188 d->m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
189 d->m_deleteButton->setToolTip(i18nc("@info", "Delete tag"));
190 d->m_deleteButton->hide();
191 connect(d->m_deleteButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::deleteTag);
192}
193
194TagEditWidget::TagEditWidget(Akonadi::TagModel *model, QWidget *parent, bool enableSelection)
195 : TagEditWidget(parent)
196{
197 setModel(model);
198 setSelectionEnabled(enableSelection);
199}
200
201TagEditWidget::~TagEditWidget() = default;
202
203void TagEditWidget::setSelectionEnabled(bool enabled)
204{
205 if (enabled == (d->m_checkableProxy != nullptr)) {
206 return;
207 }
208
209 if (enabled) {
210 d->m_checkableProxy.reset(new KCheckableProxyModel(this));
211 if (d->m_model) {
212 d->initCheckableProxy(d->m_model);
213 }
214 d->ui.tagsView->setModel(d->m_checkableProxy.get());
215 } else {
216 d->m_checkableProxy.reset();
217 d->ui.tagsView->setModel(d->m_model);
218 }
219 d->ui.selectLabel->setVisible(enabled);
220}
221
222void TagEditWidget::setModel(TagModel *model)
223{
224 if (d->m_model) {
225 disconnect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted);
226 disconnect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated);
227 }
228
229 d->m_model = model;
230 if (d->m_model) {
231 connect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted);
232 if (d->m_checkableProxy) {
233 d->initCheckableProxy(d->m_model);
234 d->ui.tagsView->setModel(d->m_checkableProxy.get());
235 } else {
236 d->ui.tagsView->setModel(d->m_model);
237 }
238 connect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated);
239 }
240}
241
242TagModel *TagEditWidget::model() const
243{
244 return d->m_model;
245}
246
247bool TagEditWidget::selectionEnabled() const
248{
249 return d->m_checkableProxy != nullptr;
250}
251
252void TagEditWidget::setSelection(const Akonadi::Tag::List &tags)
253{
254 d->m_tags = tags;
255 d->select(QModelIndex(), 0, d->m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
256}
257
258Akonadi::Tag::List TagEditWidget::selection() const
259{
260 if (!d->m_checkableProxy) {
261 return {};
262 }
263
265 for (int i = 0; i < d->m_checkableProxy->rowCount(); ++i) {
266 if (d->m_checkableProxy->selectionModel()->isRowSelected(i, QModelIndex())) {
267 const auto index = d->m_checkableProxy->index(i, 0, QModelIndex());
268 const auto tag = index.data(TagModel::TagRole).value<Tag>();
269 list.push_back(tag);
270 }
271 }
272 return list;
273}
274
275bool TagEditWidget::eventFilter(QObject *watched, QEvent *event)
276{
277 if ((watched == d->ui.tagsView) && (event->type() == QEvent::Leave)) {
278 d->m_deleteButton->hide();
279 }
281}
282
283#include "tageditwidget.moc"
284
285#include "moc_tageditwidget.cpp"
Job that creates a new tag in the Akonadi storage.
Job that deletes tags.
A widget that offers facilities to add/remove tags and optionally provides a way to select tags.
An Akonadi Tag.
Definition tag.h:26
int error() const
void finished(KJob *job)
Q_SCRIPTABLE Q_NOREPLY void start()
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Helper integration between Akonadi and Qt.
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem cancel()
KGuiItem del()
const QList< QKeySequence > & end()
void clicked(bool checked)
void rowsInserted(const QModelIndex &parent, int first, int last)
void entered(const QModelIndex &index)
QIcon fromTheme(const QString &name)
void select(const QModelIndex &topLeft, const QModelIndex &bottomRight)
void returnPressed()
void textEdited(const QString &text)
bool contains(const AT &value) const const
void push_back(parameter_type value)
QVariant data(int role) const const
bool isValid() const const
Q_OBJECTQ_OBJECT
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
T qobject_cast(QObject *object)
int height() const const
int right() const const
int top() const const
T * get() const const
QString simplified() const const
UserRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString toString() const const
T value() const const
virtual bool event(QEvent *event) override
void move(const QPoint &)
void setupUi(QWidget *widget)
void show()
void resize(const QSize &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:38 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.