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 <KLineEditEventHandler>
19#include <KLocalizedString>
20#include <KMessageBox>
21
22#include <QEvent>
23#include <QPushButton>
24
25using namespace Akonadi;
26
27class Akonadi::TagEditWidgetPrivate : public QObject
28{
30public:
31 explicit TagEditWidgetPrivate(QWidget *parent);
32
33public Q_SLOTS:
34 void slotTextEdited(const QString &text);
35 void slotItemEntered(const QModelIndex &index);
36 void deleteTag();
37 void slotCreateTag();
38 void slotCreateTagFinished(KJob *job);
39 void onRowsInserted(const QModelIndex &parent, int start, int end);
40 void onModelPopulated();
41
42public:
43 void initCheckableProxy(Akonadi::TagModel *model)
44 {
45 Q_ASSERT(m_checkableProxy);
46
47 auto selectionModel = new QItemSelectionModel(model, m_checkableProxy.get());
48 m_checkableProxy->setSourceModel(model);
49 m_checkableProxy->setSelectionModel(selectionModel);
50 }
51
52 void select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const;
53 enum ItemType {
54 UrlTag = Qt::UserRole + 1,
55 };
56
57 QWidget *const d;
58 Ui::TagEditWidget ui;
59
60 Akonadi::Tag::List m_tags;
61 Akonadi::TagModel *m_model = nullptr;
63 QModelIndex m_deleteCandidate;
64
65 QPushButton *m_deleteButton = nullptr;
66};
67
68TagEditWidgetPrivate::TagEditWidgetPrivate(QWidget *parent)
69 : d(parent)
70{
71}
72
73void TagEditWidgetPrivate::select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const
74{
75 if (!m_model) {
76 return;
77 }
78
79 QItemSelection selection;
80 for (int i = start; i <= end; i++) {
81 const QModelIndex index = m_model->index(i, 0, parent);
82 const auto insertedTag = index.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>();
83 if (m_tags.contains(insertedTag)) {
84 selection.select(index, index);
85 }
86 }
87 if (m_checkableProxy) {
88 m_checkableProxy->selectionModel()->select(selection, selectionFlag);
89 }
90}
91
92void TagEditWidgetPrivate::onModelPopulated()
93{
94 select(QModelIndex(), 0, m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
95}
96
97void TagEditWidgetPrivate::onRowsInserted(const QModelIndex &parent, int start, int end)
98{
100}
101
102void TagEditWidgetPrivate::slotCreateTag()
103{
104 if (ui.newTagButton->isEnabled()) {
105 auto createJob = new TagCreateJob(Akonadi::Tag(ui.newTagEdit->text()), this);
106 connect(createJob, &TagCreateJob::finished, this, &TagEditWidgetPrivate::slotCreateTagFinished);
107
108 ui.newTagEdit->clear();
109 ui.newTagEdit->setEnabled(false);
110 ui.newTagButton->setEnabled(false);
111 }
112}
113
114void TagEditWidgetPrivate::slotCreateTagFinished(KJob *job)
115{
116 if (job->error()) {
117 KMessageBox::error(d, i18n("Failed to create a new tag"), i18nc("@title:window", "An Error Occurred while Creating a New Tag"));
118 }
119
120 ui.newTagEdit->setEnabled(true);
121}
122
123void TagEditWidgetPrivate::slotTextEdited(const QString &text)
124{
125 // Remove unnecessary spaces from a new tag is
126 // mandatory, as the user cannot see the difference
127 // between a tag "Test" and "Test ".
128 const QString tagText = text.simplified();
129 if (tagText.isEmpty()) {
130 ui.newTagButton->setEnabled(false);
131 return;
132 }
133
134 // Check whether the new tag already exists
135 bool exists = false;
136 for (int i = 0, count = m_model->rowCount(); i < count; ++i) {
137 const QModelIndex index = m_model->index(i, 0, QModelIndex());
138 if (index.data(Qt::DisplayRole).toString() == tagText) {
139 exists = true;
140 break;
141 }
142 }
143 ui.newTagButton->setEnabled(!exists);
144}
145
146void TagEditWidgetPrivate::slotItemEntered(const QModelIndex &index)
147{
148 // align the delete-button to stay on the right border
149 // of the item
150 const QRect rect = ui.tagsView->visualRect(index);
151 const int size = rect.height();
152 const int x = rect.right() - size;
153 const int y = rect.top();
154 m_deleteButton->move(x, y);
155 m_deleteButton->resize(size, size);
156
157 m_deleteCandidate = index;
158 m_deleteButton->show();
159}
160
161void TagEditWidgetPrivate::deleteTag()
162{
163 Q_ASSERT(m_deleteCandidate.isValid());
164 const auto tag = m_deleteCandidate.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>();
165 const QString text = xi18nc("@info", "Do you really want to remove the tag <resource>%1</resource>?", tag.name());
166 const QString caption = i18nc("@title:window", "Delete Tag");
167 if (KMessageBox::questionTwoActions(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::ButtonCode::PrimaryAction) {
168 new TagDeleteJob(tag, this);
169 }
170}
171
172TagEditWidget::TagEditWidget(QWidget *parent)
173 : QWidget(parent)
174 , d(new TagEditWidgetPrivate(this))
175{
176 d->ui.setupUi(this);
178
179 d->ui.tagsView->installEventFilter(this);
180 connect(d->ui.tagsView, &QAbstractItemView::entered, d.get(), &TagEditWidgetPrivate::slotItemEntered);
181
182 connect(d->ui.newTagEdit, &QLineEdit::textEdited, d.get(), &TagEditWidgetPrivate::slotTextEdited);
183 connect(d->ui.newTagEdit, &QLineEdit::returnPressed, d.get(), &TagEditWidgetPrivate::slotCreateTag);
184 connect(d->ui.newTagButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::slotCreateTag);
185
186 // create the delete button, which is shown when
187 // hovering the items
188 d->m_deleteButton = new QPushButton(d->ui.tagsView->viewport());
189 d->m_deleteButton->setObjectName(QLatin1StringView("tagDeleteButton"));
190 d->m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
191 d->m_deleteButton->setToolTip(i18nc("@info", "Delete tag"));
192 d->m_deleteButton->hide();
193 connect(d->m_deleteButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::deleteTag);
194}
195
196TagEditWidget::TagEditWidget(Akonadi::TagModel *model, QWidget *parent, bool enableSelection)
197 : TagEditWidget(parent)
198{
199 setModel(model);
200 setSelectionEnabled(enableSelection);
201}
202
203TagEditWidget::~TagEditWidget() = default;
204
205void TagEditWidget::setSelectionEnabled(bool enabled)
206{
207 if (enabled == (d->m_checkableProxy != nullptr)) {
208 return;
209 }
210
211 if (enabled) {
212 d->m_checkableProxy.reset(new KCheckableProxyModel(this));
213 if (d->m_model) {
214 d->initCheckableProxy(d->m_model);
215 }
216 d->ui.tagsView->setModel(d->m_checkableProxy.get());
217 } else {
218 d->m_checkableProxy.reset();
219 d->ui.tagsView->setModel(d->m_model);
220 }
221 d->ui.selectLabel->setVisible(enabled);
222}
223
224void TagEditWidget::setModel(TagModel *model)
225{
226 if (d->m_model) {
227 disconnect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted);
228 disconnect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated);
229 }
230
231 d->m_model = model;
232 if (d->m_model) {
233 connect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted);
234 if (d->m_checkableProxy) {
235 d->initCheckableProxy(d->m_model);
236 d->ui.tagsView->setModel(d->m_checkableProxy.get());
237 } else {
238 d->ui.tagsView->setModel(d->m_model);
239 }
240 connect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated);
241 }
242}
243
244TagModel *TagEditWidget::model() const
245{
246 return d->m_model;
247}
248
249bool TagEditWidget::selectionEnabled() const
250{
251 return d->m_checkableProxy != nullptr;
252}
253
254void TagEditWidget::setSelection(const Akonadi::Tag::List &tags)
255{
256 d->m_tags = tags;
257 d->select(QModelIndex(), 0, d->m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
258}
259
260Akonadi::Tag::List TagEditWidget::selection() const
261{
262 if (!d->m_checkableProxy) {
263 return {};
264 }
265
267 for (int i = 0; i < d->m_checkableProxy->rowCount(); ++i) {
268 if (d->m_checkableProxy->selectionModel()->isRowSelected(i, QModelIndex())) {
269 const auto index = d->m_checkableProxy->index(i, 0, QModelIndex());
270 const auto tag = index.data(TagModel::TagRole).value<Tag>();
271 list.push_back(tag);
272 }
273 }
274 return list;
275}
276
277bool TagEditWidget::eventFilter(QObject *watched, QEvent *event)
278{
279 if ((watched == d->ui.tagsView) && (event->type() == QEvent::Leave)) {
280 d->m_deleteButton->hide();
281 }
282 return QWidget::eventFilter(watched, event);
283}
284
285#include "tageditwidget.moc"
286
287#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.
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
void catchReturnKey(QObject *lineEdit)
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()
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
int height() const const
int right() const const
int top() const const
T * get() const const
bool isEmpty() 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 Fri Jul 26 2024 11:52:53 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.