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