Krita Source Code Documentation
Loading...
Searching...
No Matches
KisTagChooserWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE project
3 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
4 * SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
5 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
6 * SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
7 * SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
8 * SPDX-FileCopyrightText: 2013 Sascha Suelzer <s.suelzer@gmail.com>
9 * SPDX-FileCopyrightText: 2020 Agata Cacko <cacko.azh@gmail.com>
10 *
11 * SPDX-License-Identifier: LGPL-2.0-or-later
12 */
13
14#include "KisTagChooserWidget.h"
15
16#include <QDebug>
17#include <QGridLayout>
18#include <QComboBox>
19#include <QMessageBox>
20
21#include <kconfig.h>
22#include <kconfiggroup.h>
23#include <ksharedconfig.h>
24#include <klocalizedstring.h>
25#include <kis_assert.h>
26#include <KisSqueezedComboBox.h>
27
28#include <KoIcon.h>
29
30#include "KisTagToolButton.h"
31#include <KisTagResourceModel.h>
32
33class Q_DECL_HIDDEN KisTagChooserWidget::Private
34{
35public:
36 QComboBox *comboBox;
40 QString resourceType;
41 QScopedPointer<KisTagModel> allTagsModel;
42};
43
44KisTagChooserWidget::KisTagChooserWidget(KisTagModel *model, QString resourceType, QWidget* parent)
45 : QWidget(parent)
46 , d(new Private)
47{
48 d->resourceType = resourceType;
49
50 d->comboBox = new QComboBox(this);
51 d->comboBox->setToolTip(i18n("Tag"));
52 d->comboBox->setSizePolicy(QSizePolicy::Policy::Expanding , QSizePolicy::Policy::Fixed);
53
54 // Allow the combo box to not depend on content size.
55 // Removing below code will cause the QComboBox when inside a QSplitter to have a width
56 // equal to the longest QComboBox item regardless of size policy.
57 d->comboBox->setMinimumContentsLength(1);
58 d->comboBox->setSizeAdjustPolicy(QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon);
59
60 d->comboBox->setInsertPolicy(QComboBox::InsertAlphabetically);
62 d->comboBox->setModel(model);
63
64 d->model = model;
65 d->allTagsModel.reset(new KisTagModel(resourceType));
66 d->allTagsModel->setTagFilter(KisTagModel::ShowAllTags);
67
68 QGridLayout* comboLayout = new QGridLayout(this);
69
70 comboLayout->addWidget(d->comboBox, 0, 0);
71
72 d->tagToolButton = new KisTagToolButton(this);
73 d->tagToolButton->setToolTip(i18n("Tag options"));
74 comboLayout->addWidget(d->tagToolButton, 0, 1);
75
76 comboLayout->setSpacing(0);
77 comboLayout->setContentsMargins(0, 0, 0, 0);
78 comboLayout->setColumnStretch(0, 3);
79 this->setEnabled(true);
80
81 connect(d->comboBox, SIGNAL(currentIndexChanged(int)),
82 this, SLOT(tagChanged(int)));
83
84 connect(d->tagToolButton, SIGNAL(popupMenuAboutToShow()),
85 this, SLOT (tagToolContextMenuAboutToShow()));
86
87 connect(d->tagToolButton, SIGNAL(newTagRequested(QString)),
88 this, SLOT(addTag(QString)));
89
90 connect(d->tagToolButton, SIGNAL(deletionOfCurrentTagRequested()),
91 this, SLOT(tagToolDeleteCurrentTag()));
92
93 connect(d->tagToolButton, SIGNAL(renamingOfCurrentTagRequested(const QString&)),
94 this, SLOT(tagToolRenameCurrentTag(const QString&)));
95
96 connect(d->tagToolButton, SIGNAL(undeletionOfTagRequested(KisTagSP)),
97 this, SLOT(tagToolUndeleteLastTag(KisTagSP)));
98
99
100 // Workaround for handling tag selection deselection when model resets.
101 // Occurs when model changes under the user e.g. +/- a resource storage.
102 connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(cacheSelectedTag()));
103 connect(d->model, SIGNAL(modelReset()), this, SLOT(restoreTagFromCache()));
104 connect(d->allTagsModel.data(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
105 this, SLOT(slotTagModelDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
106
107}
108
113
115{
116 KisTagSP currentTag = currentlySelectedTag();
117 if (!currentTag.isNull() && currentTag->id() >= 0) {
118 d->model->setTagInactive(currentTag);
120 d->model->sort(KisAllTagsModel::Name);
121 }
122}
123
125{
126 if (tagIndex >= 0) {
128 d->tagToolButton->setCurrentTag(tag);
129 KConfigGroup group = KSharedConfig::openConfig()->group("SelectedTags");
130 group.writeEntry(d->resourceType, currentlySelectedTag()->url());
131 d->model->sort(KisAllTagsModel::Name);
132 Q_EMIT sigTagChosen(tag);
133 } else {
135 }
136}
137
139{
141 bool canRenameCurrentTag = !tag.isNull() && (tagName != tag->name());
142
143 if (tagName == KisAllTagsModel::urlAll() || tagName == KisAllTagsModel::urlAllUntagged()) {
144 QMessageBox::information(this, i18nc("Dialog title", "Can't rename the tag"), i18nc("Dialog message", "You can't use this name for your custom tags."), QMessageBox::Ok);
145 return;
146 }
147
148 bool result = false;
149
150 if (canRenameCurrentTag && !tagName.isEmpty()) {
151 result = d->model->renameTag(tag, tagName, false);
152
153 if (!result) {
154 KisTagSP tagToRemove = d->model->tagForUrl(tagName);
155
156 if (tagToRemove &&
157 QMessageBox::question(this, i18nc("Dialog title", "Remove existing tag with that name?"),
158 i18nc("Dialog message (the arguments are both somewhat user readable nouns or adjectives (names of the tags), can be treated as nouns since they represent the tags)",
159 "A tag with this unique name already exists. In order to continue renaming, the existing tag needs to be removed. Do you want to continue?\n"
160 "Tag to be removed: %1\n"
161 "Tag's unique name: %2", tagToRemove->name(), tagToRemove->url()), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Cancel) {
162 result = d->model->renameTag(tag, tagName, true);
164 }
165 }
166 }
167
168 if (result) {
169 KisTagSP renamedTag = d->model->tagForUrl(tagName);
171 const QModelIndex idx = d->model->indexForTag(renamedTag);
172 setCurrentIndex(idx.row());
173 }
174}
175
177{
178 int previousIndex = d->comboBox->currentIndex();
179
180 bool success = d->model->setTagActive(tag);
181 setCurrentIndex(previousIndex);
182 if (success) {
183 setCurrentItem(tag->name());
184 d->model->sort(KisAllTagsModel::Name);
185 }
186}
187
192
194{
195 if (d->cachedTag) {
196 QModelIndex cachedIndex = d->model->indexForTag(d->cachedTag);
197 setCurrentIndex(cachedIndex.row());
198 d->cachedTag = nullptr;
199 }
200}
201
202void KisTagChooserWidget::slotTagModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> roles)
203{
204 // we care only about the check status
205 if (!roles.isEmpty() && !roles.contains(Qt::CheckStateRole)) {
206 return;
207 }
208
209 const QModelIndex currIdx =
210 d->allTagsModel->indexForTag(d->tagToolButton->undeletionCandidate());
211
212 if (currIdx.isValid() &&
213 currIdx.row() >= topLeft.row() && currIdx.row() <= bottomRight.row() &&
214 currIdx.column() >= topLeft.column() && currIdx.column() <= bottomRight.column()) {
215
216 const bool isNowActive = d->allTagsModel->data(currIdx, Qt::CheckStateRole).toBool();
217
218 if (isNowActive) {
219 d->tagToolButton->setUndeletionCandidate(KisTagSP());
220 }
221 }
222
223 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
224 for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
225 const QModelIndex idx = d->allTagsModel->index(row, column);
226
227 const bool isActive = d->allTagsModel->data(idx, Qt::CheckStateRole).toBool();
228
229 if (idx != currIdx && !isActive) {
230 d->tagToolButton->setUndeletionCandidate(d->allTagsModel->tagForIndex(idx));
231 break;
232 }
233 }
234 }
235}
236
238{
239 d->comboBox->setCurrentIndex(index);
240}
241
243{
244 return d->comboBox->currentIndex();
245}
246
248{
249 for (int i = 0; i < d->model->rowCount(); i++) {
250 QModelIndex index = d->model->index(i, 0);
251 QString currentRowTag = d->model->data(index, Qt::UserRole + KisAllTagsModel::Url).toString();
252 if (currentRowTag == tag) {
254 }
255 }
256}
257
258void KisTagChooserWidget::addTag(const QString &tag)
259{
260 addTag(tag, 0);
261}
262
264{
265 QString undeleteOption = !tagIsActive ? i18nc("Option in a dialog to undelete (reactivate) existing tag with its old assigned resources", "Restore previous tag")
266 : i18nc("Option in a dialog to use existing tag with its old assigned resources", "Use existing tag");
267 // if you use this simple cast, the order of buttons must match order of options in the enum
268 return (KisTagChooserWidget::OverwriteDialogOptions)QMessageBox::question(parent, i18nc("Dialog title", "Overwrite tag?"), i18nc("Question to the user in a dialog about creating a tag",
269 "A tag with this unique name already exists. Do you want to replace it?"),
270 i18nc("Option in a dialog to discard the previously existing tag and creating a new one in its place", "Replace (overwrite) tag"),
271 undeleteOption, i18n("Cancel"));
272}
273
274void KisTagChooserWidget::addTag(const QString &tagName, KoResourceSP resource)
275{
276 if (tagName == KisAllTagsModel::urlAll() || tagName == KisAllTagsModel::urlAllUntagged()) {
277 QMessageBox::information(this, i18nc("Dialog title", "Can't create the tag"), i18nc("Dialog message", "You can't use this name for your custom tags."), QMessageBox::Ok);
278 return;
279 }
280
281 if (tagName.isEmpty()) return;
282
283 KisTagSP tagForUrl = d->model->tagForUrl(tagName);
284 if (!tagForUrl.isNull()) {
285 int response = overwriteTagDialog(this, tagForUrl->active());
286 if (response == Undelete) { // Undelete
287 d->model->setTagActive(tagForUrl);
288 if (!resource.isNull()) {
289 KisTagResourceModel(d->resourceType).tagResources(tagForUrl, QVector<int>() << resource->resourceId());
290 }
291 d->model->sort(KisAllTagsModel::Name);
292 return;
293 } else if (response == Cancel) { // Cancel
294 return;
295 }
296 }
297 QVector<KoResourceSP> resources = (resource.isNull() ? QVector<KoResourceSP>() : (QVector<KoResourceSP>() << resource));
298 d->model->addTag(tagName, true, resources); // this will overwrite the tag
299 d->model->sort(KisAllTagsModel::Name);
300}
301
303{
304 if (tag->name() == KisAllTagsModel::urlAll() || tag->name() == KisAllTagsModel::urlAllUntagged()) {
305 QMessageBox::information(this, i18nc("Dialog title", "Can't rename the tag"), i18nc("Dialog message", "You can't use this name for your custom tags."), QMessageBox::Ok);
306 return;
307 }
308
309 KisTagSP tagForUrl = d->model->tagForUrl(tag->url());
310 if (!tagForUrl.isNull()) {
311 int response = overwriteTagDialog(this, tagForUrl->active());
312 if (response == Undelete) { // Undelete
313 d->model->setTagActive(tagForUrl);
314 if (!resource.isNull()) {
315 KisTagResourceModel(d->resourceType).tagResources(tagForUrl, QVector<int>() << resource->resourceId());
316 }
317 d->model->sort(KisAllTagsModel::Name);
318 return;
319 } else if (response == Cancel) { // Cancel
320 return;
321 }
322 }
323 QVector<KoResourceSP> resources = (resource.isNull() ? QVector<KoResourceSP>() : (QVector<KoResourceSP>() << resource));
324 d->model->addTag(tag, true, resources); // this will overwrite the tag
325 d->model->sort(KisAllTagsModel::Name);
326}
327
329{
330 int row = d->comboBox->currentIndex();
331 if (row < 0) {
332 return nullptr;
333 }
334
335 QModelIndex index = d->model->index(row, 0);
336 KisTagSP tag = d->model->tagForIndex(index);
337 return tag;
338}
339
341{
342 d->tagToolButton->loadIcon();
343}
344
346{
347 /* only enable the save button if the selected tag set is editable */
348 if (currentlySelectedTag()) {
349 d->tagToolButton->readOnlyMode(currentlySelectedTag()->id() < 0);
350 }
351 else {
352 d->tagToolButton->readOnlyMode(true);
353 }
354}
QSharedPointer< KisTag > KisTagSP
Definition KisTag.h:20
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static QString urlAll()
Definition KisTagModel.h:92
static QString urlAllUntagged()
Definition KisTagModel.h:93
The KisTagChooserWidget class is responsible for all the logic that the tags combobox has in various ...
void updateIcons()
update icon files on loading and theme change
void tagChanged(int index)
tagChanged slot for the signal from the combobox that the index changed
KisTagToolButton * tagToolButton
void slotTagModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > roles)
void tagToolDeleteCurrentTag()
tagToolDeleteCurrentTag slot for the signal from the KisTagToolButton that the current tag needs to b...
KisTagChooserWidget(KisTagModel *model, QString resourceType, QWidget *parent)
void addTag(const QString &tag)
tagToolCreateNewTag slot for the signal from KisTagToolButton that a new tag needs to be created
OverwriteDialogOptions overwriteTagDialog(KisTagChooserWidget *parent, bool undelete)
int currentIndex() const
currentIndex returns the current index in tags combobox
void tagToolRenameCurrentTag(const QString &tag)
tagToolRenameCurrentTag slot for the signal from KisTagToolButton that the current tag needs to be re...
void restoreTagFromCache()
restoreTagFromCache slot designed to restore a selected tag from previously cached selection.
void tagToolUndeleteLastTag(KisTagSP tag)
tagToolUndeleteLastTag slot for the signal from the KisTagToolButton that the last deleted tag needs ...
void sigTagChosen(const KisTagSP tag)
sigTagChosen is emitted when the selected tag in the combobox changes due to user interaction or by o...
void setCurrentItem(const QString &tag)
setCurrentItem sets the tag from the param as the current tag in the combobox
QScopedPointer< KisTagModel > allTagsModel
void tagToolContextMenuAboutToShow()
tagToolContextMenuAboutToShow slot for the signal from the KisTagToolButton that the popup will be sh...
KisTagSP currentlySelectedTag()
currentlySelectedTag returns the current tag from combobox
void setCurrentIndex(int index)
setCurrentIndex sets the current index in the combobox
void cacheSelectedTag()
cacheSelectedTag slot that stores current tag selection.
The KisTagResourceModel class makes it possible to retrieve the resources for certain tags or the tag...
bool tagResources(const KisTagSP tag, const QVector< int > &resourceIds) override
The KisTagToolButton class manages the logic of the tag management popup.
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128