File indexing completed on 2024-05-12 15:59:58

0001 /*
0002  *    This file is part of the KDE project
0003  *    SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0004  *    SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
0005  *    SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
0006  *    SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
0007  *    SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
0008  *    SPDX-FileCopyrightText: 2013 Sascha Suelzer <s.suelzer@gmail.com>
0009  *    SPDX-FileCopyrightText: 2020 Agata Cacko <cacko.azh@gmail.com>
0010  *
0011  *    SPDX-License-Identifier: LGPL-2.0-or-later
0012  */
0013 
0014 #include "KisTagChooserWidget.h"
0015 
0016 #include <QDebug>
0017 #include <QToolButton>
0018 #include <QGridLayout>
0019 #include <QComboBox>
0020 #include <QMessageBox>
0021 
0022 #include <kconfig.h>
0023 #include <kconfiggroup.h>
0024 #include <ksharedconfig.h>
0025 #include <klocalizedstring.h>
0026 #include <KisSqueezedComboBox.h>
0027 
0028 #include <KoIcon.h>
0029 
0030 #include "KisResourceItemChooserContextMenu.h"
0031 #include "KisTagToolButton.h"
0032 #include "kis_debug.h"
0033 #include <KisTagResourceModel.h>
0034 
0035 class Q_DECL_HIDDEN KisTagChooserWidget::Private
0036 {
0037 public:
0038     QComboBox *comboBox;
0039     KisTagToolButton *tagToolButton;
0040     KisTagModel *model;
0041     KisTagSP cachedTag;
0042     QString resourceType;
0043     QScopedPointer<KisTagModel> allTagsModel;
0044 };
0045 
0046 KisTagChooserWidget::KisTagChooserWidget(KisTagModel *model, QString resourceType, QWidget* parent)
0047     : QWidget(parent)
0048     , d(new Private)
0049 {
0050     d->resourceType = resourceType;
0051 
0052     d->comboBox = new QComboBox(this);
0053 
0054     d->comboBox->setToolTip(i18n("Tag"));
0055     d->comboBox->setSizePolicy(QSizePolicy::MinimumExpanding , QSizePolicy::Fixed);
0056     d->comboBox->setInsertPolicy(QComboBox::InsertAlphabetically);
0057     model->sort(KisAllTagsModel::Name);
0058     d->comboBox->setModel(model);
0059 
0060     d->model = model;
0061     d->allTagsModel.reset(new KisTagModel(resourceType));
0062     d->allTagsModel->setTagFilter(KisTagModel::ShowAllTags);
0063 
0064     QGridLayout* comboLayout = new QGridLayout(this);
0065 
0066     comboLayout->addWidget(d->comboBox, 0, 0);
0067 
0068     d->tagToolButton = new KisTagToolButton(this);
0069     d->tagToolButton->setToolTip(i18n("Tag options"));
0070     comboLayout->addWidget(d->tagToolButton, 0, 1);
0071 
0072     comboLayout->setSpacing(0);
0073     comboLayout->setMargin(0);
0074     comboLayout->setColumnStretch(0, 3);
0075     this->setEnabled(true);
0076 
0077     connect(d->comboBox, SIGNAL(currentIndexChanged(int)),
0078             this, SLOT(tagChanged(int)));
0079 
0080     connect(d->tagToolButton, SIGNAL(popupMenuAboutToShow()),
0081             this, SLOT (tagToolContextMenuAboutToShow()));
0082 
0083     connect(d->tagToolButton, SIGNAL(newTagRequested(QString)),
0084             this, SLOT(addTag(QString)));
0085 
0086     connect(d->tagToolButton, SIGNAL(deletionOfCurrentTagRequested()),
0087             this, SLOT(tagToolDeleteCurrentTag()));
0088 
0089     connect(d->tagToolButton, SIGNAL(renamingOfCurrentTagRequested(const QString&)),
0090             this, SLOT(tagToolRenameCurrentTag(const QString&)));
0091 
0092     connect(d->tagToolButton, SIGNAL(undeletionOfTagRequested(KisTagSP)),
0093             this, SLOT(tagToolUndeleteLastTag(KisTagSP)));
0094 
0095 
0096     // Workaround for handling tag selection deselection when model resets.
0097     // Occurs when model changes under the user e.g. +/- a resource storage.
0098     connect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(cacheSelectedTag()));
0099     connect(d->model, SIGNAL(modelReset()), this, SLOT(restoreTagFromCache()));
0100     connect(d->allTagsModel.data(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
0101             this, SLOT(slotTagModelDataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
0102 
0103 }
0104 
0105 KisTagChooserWidget::~KisTagChooserWidget()
0106 {
0107     delete d;
0108 }
0109 
0110 void KisTagChooserWidget::tagToolDeleteCurrentTag()
0111 {
0112     KisTagSP currentTag = currentlySelectedTag();
0113     if (!currentTag.isNull() && currentTag->id() >= 0) {
0114         d->model->setTagInactive(currentTag);
0115         setCurrentIndex(0);
0116         d->model->sort(KisAllTagsModel::Name);
0117     }
0118 }
0119 
0120 void KisTagChooserWidget::tagChanged(int tagIndex)
0121 {
0122     if (tagIndex >= 0) {
0123         KisTagSP tag = currentlySelectedTag();
0124         d->tagToolButton->setCurrentTag(tag);
0125         KConfigGroup group =  KSharedConfig::openConfig()->group("SelectedTags");
0126         group.writeEntry(d->resourceType, currentlySelectedTag()->url());
0127         d->model->sort(KisAllTagsModel::Name);
0128         emit sigTagChosen(tag);
0129     } else {
0130         setCurrentIndex(0);
0131     }
0132 }
0133 
0134 void KisTagChooserWidget::tagToolRenameCurrentTag(const QString& tagName)
0135 {
0136     KisTagSP tag = currentlySelectedTag();
0137     bool canRenameCurrentTag = !tag.isNull() && (tagName != tag->name());
0138 
0139     if (tagName == KisAllTagsModel::urlAll() || tagName == KisAllTagsModel::urlAllUntagged()) {
0140         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);
0141         return;
0142     }
0143 
0144     bool result = false;
0145 
0146     if (canRenameCurrentTag && !tagName.isEmpty()) {
0147          result = d->model->renameTag(tag, tagName, false);
0148 
0149         if (!result) {
0150             KisTagSP tagToRemove = d->model->tagForUrl(tagName);
0151 
0152             if (tagToRemove &&
0153                 QMessageBox::question(this, i18nc("Dialog title", "Remove existing tag with that name?"),
0154                 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)",
0155                 "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"
0156                 "Tag to be removed: %1\n"
0157                 "Tag's unique name: %2", tagToRemove->name(), tagToRemove->url()), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Cancel) {
0158                 result = d->model->renameTag(tag, tagName, true);
0159                 KIS_SAFE_ASSERT_RECOVER_RETURN(result);
0160             }
0161         }
0162     }
0163 
0164     if (result) {
0165         KisTagSP renamedTag = d->model->tagForUrl(tagName);
0166         KIS_SAFE_ASSERT_RECOVER_RETURN(renamedTag);
0167         const QModelIndex idx = d->model->indexForTag(renamedTag);
0168         setCurrentIndex(idx.row());
0169     }
0170 }
0171 
0172 void KisTagChooserWidget::tagToolUndeleteLastTag(KisTagSP tag)
0173 {
0174     int previousIndex = d->comboBox->currentIndex();
0175 
0176     bool success = d->model->setTagActive(tag);
0177     setCurrentIndex(previousIndex);
0178     if (success) {
0179         setCurrentItem(tag->name());
0180         d->model->sort(KisAllTagsModel::Name);
0181     }
0182 }
0183 
0184 void KisTagChooserWidget::cacheSelectedTag()
0185 {
0186     d->cachedTag = currentlySelectedTag();
0187 }
0188 
0189 void KisTagChooserWidget::restoreTagFromCache()
0190 {
0191     if (d->cachedTag) {
0192         QModelIndex cachedIndex = d->model->indexForTag(d->cachedTag);
0193         setCurrentIndex(cachedIndex.row());
0194         d->cachedTag = nullptr;
0195     }
0196 }
0197 
0198 void KisTagChooserWidget::slotTagModelDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> roles)
0199 {
0200     // we care only about the check status
0201     if (!roles.isEmpty() && !roles.contains(Qt::CheckStateRole)) {
0202         return;
0203     }
0204 
0205     const QModelIndex currIdx =
0206             d->allTagsModel->indexForTag(d->tagToolButton->undeletionCandidate());
0207 
0208     if (currIdx.isValid() &&
0209         currIdx.row() >= topLeft.row() && currIdx.row() <= bottomRight.row() &&
0210         currIdx.column() >= topLeft.column() && currIdx.column() <= bottomRight.column()) {
0211 
0212         const bool isNowActive = d->allTagsModel->data(currIdx, Qt::CheckStateRole).toBool();
0213 
0214         if (isNowActive) {
0215             d->tagToolButton->setUndeletionCandidate(KisTagSP());
0216         }
0217     }
0218 
0219     for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
0220         for (int column = topLeft.column(); column <= bottomRight.column(); column++) {
0221             const QModelIndex idx = d->allTagsModel->index(row, column);
0222 
0223             const bool isActive = d->allTagsModel->data(idx, Qt::CheckStateRole).toBool();
0224 
0225             if (idx != currIdx && !isActive) {
0226                 d->tagToolButton->setUndeletionCandidate(d->allTagsModel->tagForIndex(idx));
0227                 break;
0228             }
0229         }
0230     }
0231 }
0232 
0233 void KisTagChooserWidget::setCurrentIndex(int index)
0234 {
0235     d->comboBox->setCurrentIndex(index);
0236 }
0237 
0238 int KisTagChooserWidget::currentIndex() const
0239 {
0240     return d->comboBox->currentIndex();
0241 }
0242 
0243 void KisTagChooserWidget::setCurrentItem(const QString &tag)
0244 {
0245     for (int i = 0; i < d->model->rowCount(); i++) {
0246         QModelIndex index = d->model->index(i, 0);
0247         QString currentRowTag = d->model->data(index, Qt::UserRole + KisAllTagsModel::Url).toString();
0248         if (currentRowTag == tag) {
0249             setCurrentIndex(i);
0250         }
0251     }
0252 }
0253 
0254 void KisTagChooserWidget::addTag(const QString &tag)
0255 {
0256     addTag(tag, 0);
0257 }
0258 
0259 KisTagChooserWidget::OverwriteDialogOptions KisTagChooserWidget::overwriteTagDialog(KisTagChooserWidget* parent, bool tagIsActive)
0260 {
0261     QString undeleteOption = !tagIsActive ? i18nc("Option in a dialog to undelete (reactivate) existing tag with its old assigned resources", "Restore previous tag")
0262                                       : i18nc("Option in a dialog to use existing tag with its old assigned resources", "Use existing tag");
0263     // if you use this simple cast, the order of buttons must match order of options in the enum
0264     return (KisTagChooserWidget::OverwriteDialogOptions)QMessageBox::question(parent, i18nc("Dialog title", "Overwrite tag?"), i18nc("Question to the user in a dialog about creating a tag",
0265                                                                                       "A tag with this unique name already exists. Do you want to replace it?"),
0266                                        i18nc("Option in a dialog to discard the previously existing tag and creating a new one in its place", "Replace (overwrite) tag"),
0267                                        undeleteOption, i18n("Cancel"));
0268 }
0269 
0270 void KisTagChooserWidget::addTag(const QString &tagName, KoResourceSP resource)
0271 {
0272     if (tagName == KisAllTagsModel::urlAll() || tagName == KisAllTagsModel::urlAllUntagged()) {
0273         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);
0274         return;
0275     }
0276 
0277     if (tagName.isEmpty()) return;
0278 
0279     KisTagSP tagForUrl = d->model->tagForUrl(tagName);
0280     if (!tagForUrl.isNull()) {
0281         int response = overwriteTagDialog(this, tagForUrl->active());
0282         if (response == Undelete) { // Undelete
0283             d->model->setTagActive(tagForUrl);
0284             if (!resource.isNull()) {
0285                 KisTagResourceModel(d->resourceType).tagResources(tagForUrl, QVector<int>() << resource->resourceId());
0286             }
0287             d->model->sort(KisAllTagsModel::Name);
0288             return;
0289         } else if (response == Cancel) { // Cancel
0290             return;
0291         }
0292     }
0293     QVector<KoResourceSP> resources = (resource.isNull() ? QVector<KoResourceSP>() : (QVector<KoResourceSP>() << resource));
0294     d->model->addTag(tagName, true, resources); // this will overwrite the tag
0295     d->model->sort(KisAllTagsModel::Name);
0296 }
0297 
0298 void KisTagChooserWidget::addTag(KisTagSP tag, KoResourceSP resource)
0299 {
0300     if (tag->name() == KisAllTagsModel::urlAll() || tag->name() == KisAllTagsModel::urlAllUntagged()) {
0301         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);
0302         return;
0303     }
0304 
0305     KisTagSP tagForUrl = d->model->tagForUrl(tag->url());
0306     if (!tagForUrl.isNull()) {
0307         int response = overwriteTagDialog(this, tagForUrl->active());
0308         if (response == Undelete) { // Undelete
0309             d->model->setTagActive(tagForUrl);
0310             if (!resource.isNull()) {
0311                 KisTagResourceModel(d->resourceType).tagResources(tagForUrl, QVector<int>() << resource->resourceId());
0312             }
0313             d->model->sort(KisAllTagsModel::Name);
0314             return;
0315         } else if (response == Cancel) { // Cancel
0316             return;
0317         }
0318     }
0319     QVector<KoResourceSP> resources = (resource.isNull() ? QVector<KoResourceSP>() : (QVector<KoResourceSP>() << resource));
0320     d->model->addTag(tag, true, resources); // this will overwrite the tag
0321     d->model->sort(KisAllTagsModel::Name);
0322 }
0323 
0324 KisTagSP KisTagChooserWidget::currentlySelectedTag()
0325 {
0326     int row = d->comboBox->currentIndex();
0327     if (row < 0) {
0328         return nullptr;
0329     }
0330 
0331     QModelIndex index = d->model->index(row, 0);
0332     KisTagSP tag =  d->model->tagForIndex(index);
0333     return tag;
0334 }
0335 
0336 void KisTagChooserWidget::updateIcons()
0337 {
0338     d->tagToolButton->loadIcon();
0339 }
0340 
0341 void KisTagChooserWidget::tagToolContextMenuAboutToShow()
0342 {
0343     /* only enable the save button if the selected tag set is editable */
0344     if (currentlySelectedTag()) {
0345         d->tagToolButton->readOnlyMode(currentlySelectedTag()->id() < 0);
0346     }
0347     else {
0348         d->tagToolButton->readOnlyMode(true);
0349     }
0350 }