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 }