File indexing completed on 2025-01-05 04:47:11
0001 /* 0002 This file is part of Akonadi 0003 0004 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 #include "tageditwidget.h" 0009 #include "changerecorder.h" 0010 #include "tagattribute.h" 0011 #include "tagcreatejob.h" 0012 #include "tagdeletejob.h" 0013 #include "tagfetchscope.h" 0014 #include "tagmodel.h" 0015 #include "ui_tageditwidget.h" 0016 0017 #include <KCheckableProxyModel> 0018 #include <KLocalizedString> 0019 #include <KMessageBox> 0020 0021 #include <QEvent> 0022 #include <QPushButton> 0023 0024 using namespace Akonadi; 0025 0026 class Akonadi::TagEditWidgetPrivate : public QObject 0027 { 0028 Q_OBJECT 0029 public: 0030 explicit TagEditWidgetPrivate(QWidget *parent); 0031 0032 public Q_SLOTS: 0033 void slotTextEdited(const QString &text); 0034 void slotItemEntered(const QModelIndex &index); 0035 void deleteTag(); 0036 void slotCreateTag(); 0037 void slotCreateTagFinished(KJob *job); 0038 void onRowsInserted(const QModelIndex &parent, int start, int end); 0039 void onModelPopulated(); 0040 0041 public: 0042 void initCheckableProxy(Akonadi::TagModel *model) 0043 { 0044 Q_ASSERT(m_checkableProxy); 0045 0046 auto selectionModel = new QItemSelectionModel(model, m_checkableProxy.get()); 0047 m_checkableProxy->setSourceModel(model); 0048 m_checkableProxy->setSelectionModel(selectionModel); 0049 } 0050 0051 void select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const; 0052 enum ItemType { 0053 UrlTag = Qt::UserRole + 1, 0054 }; 0055 0056 QWidget *const d; 0057 Ui::TagEditWidget ui; 0058 0059 Akonadi::Tag::List m_tags; 0060 Akonadi::TagModel *m_model = nullptr; 0061 QScopedPointer<KCheckableProxyModel> m_checkableProxy; 0062 QModelIndex m_deleteCandidate; 0063 0064 QPushButton *m_deleteButton = nullptr; 0065 }; 0066 0067 TagEditWidgetPrivate::TagEditWidgetPrivate(QWidget *parent) 0068 : d(parent) 0069 { 0070 } 0071 0072 void TagEditWidgetPrivate::select(const QModelIndex &parent, int start, int end, QItemSelectionModel::SelectionFlag selectionFlag) const 0073 { 0074 if (!m_model) { 0075 return; 0076 } 0077 0078 QItemSelection selection; 0079 for (int i = start; i <= end; i++) { 0080 const QModelIndex index = m_model->index(i, 0, parent); 0081 const auto insertedTag = index.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>(); 0082 if (m_tags.contains(insertedTag)) { 0083 selection.select(index, index); 0084 } 0085 } 0086 if (m_checkableProxy) { 0087 m_checkableProxy->selectionModel()->select(selection, selectionFlag); 0088 } 0089 } 0090 0091 void TagEditWidgetPrivate::onModelPopulated() 0092 { 0093 select(QModelIndex(), 0, m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect); 0094 } 0095 0096 void TagEditWidgetPrivate::onRowsInserted(const QModelIndex &parent, int start, int end) 0097 { 0098 select(parent, start, end, QItemSelectionModel::Select); 0099 } 0100 0101 void TagEditWidgetPrivate::slotCreateTag() 0102 { 0103 if (ui.newTagButton->isEnabled()) { 0104 auto createJob = new TagCreateJob(Akonadi::Tag(ui.newTagEdit->text()), this); 0105 connect(createJob, &TagCreateJob::finished, this, &TagEditWidgetPrivate::slotCreateTagFinished); 0106 0107 ui.newTagEdit->clear(); 0108 ui.newTagEdit->setEnabled(false); 0109 ui.newTagButton->setEnabled(false); 0110 } 0111 } 0112 0113 void TagEditWidgetPrivate::slotCreateTagFinished(KJob *job) 0114 { 0115 if (job->error()) { 0116 KMessageBox::error(d, i18n("Failed to create a new tag"), i18nc("@title:window", "An Error Occurred while Creating a New Tag")); 0117 } 0118 0119 ui.newTagEdit->setEnabled(true); 0120 } 0121 0122 void TagEditWidgetPrivate::slotTextEdited(const QString &text) 0123 { 0124 // Remove unnecessary spaces from a new tag is 0125 // mandatory, as the user cannot see the difference 0126 // between a tag "Test" and "Test ". 0127 const QString tagText = text.simplified(); 0128 if (tagText.isEmpty()) { 0129 ui.newTagButton->setEnabled(false); 0130 return; 0131 } 0132 0133 // Check whether the new tag already exists 0134 bool exists = false; 0135 for (int i = 0, count = m_model->rowCount(); i < count; ++i) { 0136 const QModelIndex index = m_model->index(i, 0, QModelIndex()); 0137 if (index.data(Qt::DisplayRole).toString() == tagText) { 0138 exists = true; 0139 break; 0140 } 0141 } 0142 ui.newTagButton->setEnabled(!exists); 0143 } 0144 0145 void TagEditWidgetPrivate::slotItemEntered(const QModelIndex &index) 0146 { 0147 // align the delete-button to stay on the right border 0148 // of the item 0149 const QRect rect = ui.tagsView->visualRect(index); 0150 const int size = rect.height(); 0151 const int x = rect.right() - size; 0152 const int y = rect.top(); 0153 m_deleteButton->move(x, y); 0154 m_deleteButton->resize(size, size); 0155 0156 m_deleteCandidate = index; 0157 m_deleteButton->show(); 0158 } 0159 0160 void TagEditWidgetPrivate::deleteTag() 0161 { 0162 Q_ASSERT(m_deleteCandidate.isValid()); 0163 const auto tag = m_deleteCandidate.data(Akonadi::TagModel::TagRole).value<Akonadi::Tag>(); 0164 const QString text = xi18nc("@info", "Do you really want to remove the tag <resource>%1</resource>?", tag.name()); 0165 const QString caption = i18nc("@title:window", "Delete Tag"); 0166 if (KMessageBox::questionTwoActions(d, text, caption, KStandardGuiItem::del(), KStandardGuiItem::cancel()) == KMessageBox::ButtonCode::PrimaryAction) { 0167 new TagDeleteJob(tag, this); 0168 } 0169 } 0170 0171 TagEditWidget::TagEditWidget(QWidget *parent) 0172 : QWidget(parent) 0173 , d(new TagEditWidgetPrivate(this)) 0174 { 0175 d->ui.setupUi(this); 0176 0177 d->ui.tagsView->installEventFilter(this); 0178 connect(d->ui.tagsView, &QAbstractItemView::entered, d.get(), &TagEditWidgetPrivate::slotItemEntered); 0179 0180 connect(d->ui.newTagEdit, &QLineEdit::textEdited, d.get(), &TagEditWidgetPrivate::slotTextEdited); 0181 connect(d->ui.newTagEdit, &QLineEdit::returnPressed, d.get(), &TagEditWidgetPrivate::slotCreateTag); 0182 connect(d->ui.newTagButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::slotCreateTag); 0183 0184 // create the delete button, which is shown when 0185 // hovering the items 0186 d->m_deleteButton = new QPushButton(d->ui.tagsView->viewport()); 0187 d->m_deleteButton->setObjectName(QLatin1StringView("tagDeleteButton")); 0188 d->m_deleteButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); 0189 d->m_deleteButton->setToolTip(i18nc("@info", "Delete tag")); 0190 d->m_deleteButton->hide(); 0191 connect(d->m_deleteButton, &QAbstractButton::clicked, d.get(), &TagEditWidgetPrivate::deleteTag); 0192 } 0193 0194 TagEditWidget::TagEditWidget(Akonadi::TagModel *model, QWidget *parent, bool enableSelection) 0195 : TagEditWidget(parent) 0196 { 0197 setModel(model); 0198 setSelectionEnabled(enableSelection); 0199 } 0200 0201 TagEditWidget::~TagEditWidget() = default; 0202 0203 void TagEditWidget::setSelectionEnabled(bool enabled) 0204 { 0205 if (enabled == (d->m_checkableProxy != nullptr)) { 0206 return; 0207 } 0208 0209 if (enabled) { 0210 d->m_checkableProxy.reset(new KCheckableProxyModel(this)); 0211 if (d->m_model) { 0212 d->initCheckableProxy(d->m_model); 0213 } 0214 d->ui.tagsView->setModel(d->m_checkableProxy.get()); 0215 } else { 0216 d->m_checkableProxy.reset(); 0217 d->ui.tagsView->setModel(d->m_model); 0218 } 0219 d->ui.selectLabel->setVisible(enabled); 0220 } 0221 0222 void TagEditWidget::setModel(TagModel *model) 0223 { 0224 if (d->m_model) { 0225 disconnect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted); 0226 disconnect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated); 0227 } 0228 0229 d->m_model = model; 0230 if (d->m_model) { 0231 connect(d->m_model, &QAbstractItemModel::rowsInserted, d.get(), &TagEditWidgetPrivate::onRowsInserted); 0232 if (d->m_checkableProxy) { 0233 d->initCheckableProxy(d->m_model); 0234 d->ui.tagsView->setModel(d->m_checkableProxy.get()); 0235 } else { 0236 d->ui.tagsView->setModel(d->m_model); 0237 } 0238 connect(d->m_model, &TagModel::populated, d.get(), &TagEditWidgetPrivate::onModelPopulated); 0239 } 0240 } 0241 0242 TagModel *TagEditWidget::model() const 0243 { 0244 return d->m_model; 0245 } 0246 0247 bool TagEditWidget::selectionEnabled() const 0248 { 0249 return d->m_checkableProxy != nullptr; 0250 } 0251 0252 void TagEditWidget::setSelection(const Akonadi::Tag::List &tags) 0253 { 0254 d->m_tags = tags; 0255 d->select(QModelIndex(), 0, d->m_model->rowCount() - 1, QItemSelectionModel::ClearAndSelect); 0256 } 0257 0258 Akonadi::Tag::List TagEditWidget::selection() const 0259 { 0260 if (!d->m_checkableProxy) { 0261 return {}; 0262 } 0263 0264 Akonadi::Tag::List list; 0265 for (int i = 0; i < d->m_checkableProxy->rowCount(); ++i) { 0266 if (d->m_checkableProxy->selectionModel()->isRowSelected(i, QModelIndex())) { 0267 const auto index = d->m_checkableProxy->index(i, 0, QModelIndex()); 0268 const auto tag = index.data(TagModel::TagRole).value<Tag>(); 0269 list.push_back(tag); 0270 } 0271 } 0272 return list; 0273 } 0274 0275 bool TagEditWidget::eventFilter(QObject *watched, QEvent *event) 0276 { 0277 if ((watched == d->ui.tagsView) && (event->type() == QEvent::Leave)) { 0278 d->m_deleteButton->hide(); 0279 } 0280 return QWidget::eventFilter(watched, event); 0281 } 0282 0283 #include "tageditwidget.moc" 0284 0285 #include "moc_tageditwidget.cpp"