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 "ui_tageditwidget.h"
10 #include "changerecorder.h"
11 #include "tagcreatejob.h"
12 #include "tagdeletejob.h"
13 #include "tagfetchscope.h"
14 #include "tagattribute.h"
15 #include "tagmodel.h"
16 
17 #include <KLocalizedString>
18 #include <KMessageBox>
19 #include <KCheckableProxyModel>
20 
21 #include <QEvent>
22 #include <QPushButton>
23 
24 using namespace Akonadi;
25 
26 class Q_DECL_HIDDEN TagEditWidget::Private : public QObject
27 {
28  Q_OBJECT
29 public:
30  explicit Private(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  QItemSelectionModel *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 TagEditWidget::Private::Private(QWidget *parent)
68  : d(parent)
69 {}
70 
71 void TagEditWidget::Private::select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const
72 {
73  if (!m_model) {
74  return;
75  }
76 
77  QItemSelection selection;
78  for (int i = start; i <= end; i++) {
79  const QModelIndex index = m_model->index(i, 0, parent);
80  const Akonadi::Tag insertedTag = index.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>();
81  if (m_tags.contains(insertedTag)) {
82  selection.select(index, index);
83  }
84  }
85  if (m_checkableProxy) {
86  m_checkableProxy->selectionModel()->select(selection, selectionFlag);
87  }
88 }
89 
90 void TagEditWidget::Private::onModelPopulated()
91 {
92  select(QModelIndex(), 0, m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
93 }
94 
95 void TagEditWidget::Private::onRowsInserted(const QModelIndex &parent, int start, int end)
96 {
97  select(parent, start, end, QItemSelectionModel::Select);
98 }
99 
100 void TagEditWidget::Private::slotCreateTag()
101 {
102  if (ui.newTagButton->isEnabled()) {
103  auto *createJob = new TagCreateJob(Akonadi::Tag(ui.newTagEdit->text()), this);
104  connect(createJob, &TagCreateJob::finished, this, &TagEditWidget::Private::slotCreateTagFinished);
105 
106  ui.newTagEdit->clear();
107  ui.newTagEdit->setEnabled(false);
108  ui.newTagButton->setEnabled(false);
109  }
110 }
111 
112 void TagEditWidget::Private::slotCreateTagFinished(KJob *job)
113 {
114  if (job->error()) {
115  KMessageBox::error(d, i18n("Failed to create a new tag"),
116  i18n("An error occurred while creating a new tag"));
117  }
118 
119  ui.newTagEdit->setEnabled(true);
120 }
121 
122 void TagEditWidget::Private::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 TagEditWidget::Private::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 TagEditWidget::Private::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",
165  "Do you really want to remove the tag <resource>%1</resource>?",
166  tag.name());
167  const QString caption = i18nc("@title", "Delete tag");
168  if (KMessageBox::questionYesNo(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::Yes) {
169  new TagDeleteJob(tag, this);
170  }
171 }
172 
173 TagEditWidget::TagEditWidget(QWidget *parent)
174  : QWidget(parent)
175  , d(new Private(this))
176 {
177  d->ui.setupUi(this);
178 
179 
180  d->ui.tagsView->installEventFilter(this);
181  connect(d->ui.tagsView, &QAbstractItemView::entered, d.get(), &Private::slotItemEntered);
182 
183  connect(d->ui.newTagEdit, &QLineEdit::textEdited, d.get(), &Private::slotTextEdited);
184  connect(d->ui.newTagEdit, &QLineEdit::returnPressed, d.get(), &Private::slotCreateTag);
185  connect(d->ui.newTagButton, &QAbstractButton::clicked, d.get(), &Private::slotCreateTag);
186 
187 
188  // create the delete button, which is shown when
189  // hovering the items
190  d->m_deleteButton = new QPushButton(d->ui.tagsView->viewport());
191  d->m_deleteButton->setObjectName(QStringLiteral("tagDeleteButton"));
192  d->m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
193  d->m_deleteButton->setToolTip(i18nc("@info", "Delete tag"));
194  d->m_deleteButton->hide();
195  connect(d->m_deleteButton, &QAbstractButton::clicked, d.data(), &Private::deleteTag);
196 }
197 
198 TagEditWidget::TagEditWidget(Akonadi::TagModel *model, QWidget *parent, bool enableSelection)
199  : TagEditWidget(parent)
200 {
201  setModel(model);
202  setSelectionEnabled(enableSelection);
203 }
204 
205 TagEditWidget::~TagEditWidget() = default;
206 
207 void TagEditWidget::setSelectionEnabled(bool enabled)
208 {
209  if (enabled == (d->m_checkableProxy != nullptr)) {
210  return;
211  }
212 
213  if (enabled) {
214  d->m_checkableProxy.reset(new KCheckableProxyModel(this));
215  if (d->m_model) {
216  d->initCheckableProxy(d->m_model);
217  }
218  d->ui.tagsView->setModel(d->m_checkableProxy.get());
219  } else {
220  d->m_checkableProxy.reset();
221  d->ui.tagsView->setModel(d->m_model);
222  }
223  d->ui.selectLabel->setVisible(enabled);
224 }
225 
226 void TagEditWidget::setModel(TagModel *model)
227 {
228  if (d->m_model) {
229  disconnect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &Private::onRowsInserted);
230  disconnect(d->m_model, &TagModel::populated, d.get(), &Private::onModelPopulated);
231  }
232 
233  d->m_model = model;
234  if (d->m_model) {
235  connect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &Private::onRowsInserted);
236  if (d->m_checkableProxy) {
237  d->initCheckableProxy(d->m_model);
238  d->ui.tagsView->setModel(d->m_checkableProxy.get());
239  } else {
240  d->ui.tagsView->setModel(d->m_model);
241  }
242  connect(d->m_model, &TagModel::populated, d.get(), &Private::onModelPopulated);
243  }
244 }
245 
246 TagModel *TagEditWidget::model() const
247 {
248  return d->m_model;
249 }
250 
251 bool TagEditWidget::selectionEnabled() const
252 {
253  return d->m_checkableProxy != nullptr;
254 }
255 
256 void TagEditWidget::setSelection(const Akonadi::Tag::List &tags)
257 {
258  d->m_tags = tags;
259  d->select(QModelIndex(), 0, d->m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect);
260 }
261 
262 Akonadi::Tag::List TagEditWidget::selection() const
263 {
264  if (!d->m_checkableProxy) {
265  return {};
266  }
267 
269  for (int i = 0; i < d->m_checkableProxy->rowCount(); ++i) {
270  if (d->m_checkableProxy->selectionModel()->isRowSelected(i, QModelIndex())) {
271  const auto index = d->m_checkableProxy->index(i, 0, QModelIndex());
272  const auto tag = index.data(TagModel::TagRole).value<Tag>();
273  list.push_back(tag);
274  }
275  }
276  return list;
277 }
278 
279 bool TagEditWidget::eventFilter(QObject *watched, QEvent *event)
280 {
281  if ((watched == d->ui.tagsView) && (event->type() == QEvent::Leave)) {
282  d->m_deleteButton->hide();
283  }
284  return QWidget::eventFilter(watched, event);
285 }
286 
287 #include "tageditwidget.moc"
288 
QString caption()
QEvent::Type type() const const
void setupUi(QWidget *widget)
int right() const const
QString xi18nc(const char *context, const char *text, const TYPE &arg...)
void select(const QModelIndex &topLeft, const QModelIndex &bottomRight)
QString simplified() const const
T value() const const
int height() const const
void start() override
Jobs are started automatically once entering the event loop again, no need to explicitly call this...
Definition: job.cpp:310
KGuiItem cancel()
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual bool event(QEvent *e)
KGuiItem del()
A widget that offers facilities to add/remove tags and optionally provides a way to select tags...
Definition: tageditwidget.h:25
int top() const const
void finished(KJob *job)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
Job that deletes tags.
Definition: tagdeletejob.h:24
void setObjectName(const QString &name)
UserRole
bool isEmpty() const const
void clicked(bool checked)
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
virtual bool eventFilter(QObject *watched, QEvent *event)
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)
void textEdited(const QString &text)
QString i18n(const char *text, const TYPE &arg...)
void returnPressed()
QVariant data(int role) const const
Helper integration between Akonadi and Qt.
Job that creates a new tag in the Akonadi storage.
Definition: tagcreatejob.h:23
An Akonadi Tag.
Definition: tag.h:26
void push_back(const T &value)
QIcon fromTheme(const QString &name)
void rowsInserted(const QModelIndex &parent, int first, int last)
ItemType
void entered(const QModelIndex &index)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
QString toString() const const
Tag::List tags() const
Returns the tags passed to the constructor.
int error() const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Jul 12 2020 23:16:58 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.