Akonadi

tageditwidget.cpp
1 /*
2  This file is part of Akonadi
3 
4  SPDX-FileCopyrightText: 2014 Christian Mollekopf <[email protected]>
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 
24 using namespace Akonadi;
25 
26 class Akonadi::TagEditWidgetPrivate : public QObject
27 {
28  Q_OBJECT
29 public:
30  explicit TagEditWidgetPrivate(QWidget *parent);
31 
32 public 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 
41 public:
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;
61  QScopedPointer<KCheckableProxyModel> m_checkableProxy;
62  QModelIndex m_deleteCandidate;
63 
64  QPushButton *m_deleteButton = nullptr;
65 };
66 
67 TagEditWidgetPrivate::TagEditWidgetPrivate(QWidget *parent)
68  : d(parent)
69 {
70 }
71 
72 void 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 
91 void TagEditWidgetPrivate::onModelPopulated()
92 {
93  select(QModelIndex(), 0, m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
94 }
95 
96 void TagEditWidgetPrivate::onRowsInserted(const QModelIndex &parent, int start, int end)
97 {
98  select(parent, start, end, QItemSelectionModel::Select);
99 }
100 
101 void 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 
113 void TagEditWidgetPrivate::slotCreateTagFinished(KJob *job)
114 {
115  if (job->error()) {
116  KMessageBox::error(d, i18n("Failed to create a new tag"), i18n("An error occurred while creating a new tag"));
117  }
118 
119  ui.newTagEdit->setEnabled(true);
120 }
121 
122 void 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 
145 void 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 
160 void 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", "Delete tag");
166  if (KMessageBox::questionYesNo(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::Yes) {
167  new TagDeleteJob(tag, this);
168  }
169 }
170 
171 TagEditWidget::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(QStringLiteral("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 
194 TagEditWidget::TagEditWidget(Akonadi::TagModel *model, QWidget *parent, bool enableSelection)
195  : TagEditWidget(parent)
196 {
197  setModel(model);
198  setSelectionEnabled(enableSelection);
199 }
200 
201 TagEditWidget::~TagEditWidget() = default;
202 
203 void 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 
222 void 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 
242 TagModel *TagEditWidget::model() const
243 {
244  return d->m_model;
245 }
246 
247 bool TagEditWidget::selectionEnabled() const
248 {
249  return d->m_checkableProxy != nullptr;
250 }
251 
252 void 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 
258 Akonadi::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 
275 bool TagEditWidget::eventFilter(QObject *watched, QEvent *event)
276 {
277  if ((watched == d->ui.tagsView) && (event->type() == QEvent::Leave)) {
278  d->m_deleteButton->hide();
279  }
280  return QWidget::eventFilter(watched, event);
281 }
282 
283 #include "tageditwidget.moc"
Q_OBJECTQ_OBJECT
KGuiItem del()
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
UserRole
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void finished(KJob *job)
Q_SLOTSQ_SLOTS
int right() const const
ItemType
An Akonadi Tag.
Definition: tag.h:25
T value() const const
void clicked(bool checked)
virtual bool event(QEvent *event) override
Q_SCRIPTABLE Q_NOREPLY void start()
QIcon fromTheme(const QString &name)
void push_back(const T &value)
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QString caption()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString simplified() const const
virtual bool eventFilter(QObject *watched, QEvent *event)
QVariant data(int role) const const
ButtonCode questionYesNo(QWidget *parent, const QString &text, const QString &caption=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
int top() const const
QString i18n(const char *text, const TYPE &arg...)
bool isEmpty() const const
void returnPressed()
void setupUi(QWidget *widget)
void select(const QModelIndex &topLeft, const QModelIndex &bottomRight)
Job that deletes tags.
Definition: tagdeletejob.h:22
void textEdited(const QString &text)
int height() const const
void rowsInserted(const QModelIndex &parent, int first, int last)
A widget that offers facilities to add/remove tags and optionally provides a way to select tags.
Definition: tageditwidget.h:29
QString i18nc(const char *context, const char *text, const TYPE &arg...)
int error() const
KGuiItem cancel()
const QList< QKeySequence > & end()
void entered(const QModelIndex &index)
QString toString() const const
Helper integration between Akonadi and Qt.
Job that creates a new tag in the Akonadi storage.
Definition: tagcreatejob.h:21
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:01:07 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.