File indexing completed on 2024-04-28 04:20:52
0001 // SPDX-FileCopyrightText: 2003 David Faure <faure@kde.org> 0002 // SPDX-FileCopyrightText: 2003-2005 Stephan Binner <binner@kde.org> 0003 // SPDX-FileCopyrightText: 2003-2022 Jesper K. Pedersen <jesper.pedersen@kdab.com> 0004 // SPDX-FileCopyrightText: 2004 Andrew Coles <andrew.i.coles@googlemail.com> 0005 // SPDX-FileCopyrightText: 2004-2007 Dirk Mueller <mueller@kde.org> 0006 // SPDX-FileCopyrightText: 2006-2007 Tuomas Suutari <tuomas@nepnep.net> 0007 // SPDX-FileCopyrightText: 2007-2008 Laurent Montel <montel@kde.org> 0008 // SPDX-FileCopyrightText: 2008 Jakob Petsovits <jpetso@gmx.at> 0009 // SPDX-FileCopyrightText: 2009 Hassan Ibraheem <hasan.ibraheem@gmail.com> 0010 // SPDX-FileCopyrightText: 2010 Jan Kundrát <jkt@flaska.net> 0011 // SPDX-FileCopyrightText: 2010-2013 Miika Turkia <miika.turkia@gmail.com> 0012 // SPDX-FileCopyrightText: 2012-2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0013 // SPDX-FileCopyrightText: 2013 Dominik Broj <broj.dominik@gmail.com> 0014 // SPDX-FileCopyrightText: 2013 Reimar Imhof <Reimar.Imhof@netcologne.de> 0015 // SPDX-FileCopyrightText: 2014-2022 Tobias Leupold <tl@stonemx.de> 0016 // SPDX-FileCopyrightText: 2015 Yuri Chornoivan <yurchor@ukr.net> 0017 // SPDX-FileCopyrightText: 2017 Raymond Wooninck <tittiatcoke@gmail.com> 0018 // 0019 // SPDX-License-Identifier: GPL-2.0-or-later 0020 0021 #include "ListSelect.h" 0022 0023 #include "CompletableLineEdit.h" 0024 #include "ListViewItemHider.h" 0025 #include "ShowSelectionOnlyManager.h" 0026 0027 #include <CategoryListView/CheckDropItem.h> 0028 #include <CategoryListView/DragableTreeWidget.h> 0029 #include <DB/CategoryItem.h> 0030 #include <DB/ImageDB.h> 0031 #include <DB/MemberMap.h> 0032 #include <kpabase/StringSet.h> 0033 0034 #include <KIO/CopyJob> 0035 #include <KLocalizedString> 0036 #include <KMessageBox> 0037 #include <QButtonGroup> 0038 #include <QHeaderView> 0039 #include <QInputDialog> 0040 #include <QLabel> 0041 #include <QLayout> 0042 #include <QList> 0043 #include <QMenu> 0044 #include <QMouseEvent> 0045 #include <QRadioButton> 0046 #include <QToolButton> 0047 #include <QWidgetAction> 0048 #include <kcompletion_version.h> 0049 #include <kwidgetsaddons_version.h> 0050 0051 using namespace AnnotationDialog; 0052 using CategoryListView::CheckDropItem; 0053 0054 namespace 0055 { 0056 inline QPixmap smallIcon(const QString &iconName) 0057 { 0058 return QIcon::fromTheme(iconName).pixmap(KIconLoader::StdSizes::SizeSmall); 0059 } 0060 } 0061 0062 AnnotationDialog::ListSelect::ListSelect(const DB::CategoryPtr &category, QWidget *parent) 0063 : QWidget(parent) 0064 , m_category(category) 0065 , m_baseTitle() 0066 { 0067 QVBoxLayout *layout = new QVBoxLayout(this); 0068 0069 m_lineEdit = new CompletableLineEdit(this); 0070 m_lineEdit->setProperty("FocusCandidate", true); 0071 m_lineEdit->setProperty("WantsFocus", true); 0072 m_lineEdit->setPlaceholderText(i18nc("@label:textbox", "Enter a tag ...")); 0073 layout->addWidget(m_lineEdit); 0074 0075 m_treeWidget = new CategoryListView::DragableTreeWidget(m_category, this); 0076 m_treeWidget->setHeaderLabel(QString::fromLatin1("items")); 0077 m_treeWidget->header()->hide(); 0078 connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemClicked, this, &ListSelect::itemSelected); 0079 m_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); 0080 connect(m_treeWidget, &CategoryListView::DragableTreeWidget::customContextMenuRequested, this, &ListSelect::showContextMenu); 0081 connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemsChanged, this, &ListSelect::rePopulate); 0082 connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemClicked, this, &ListSelect::updateSelectionCount); 0083 0084 layout->addWidget(m_treeWidget); 0085 0086 // Merge CheckBox 0087 QHBoxLayout *lay2 = new QHBoxLayout; 0088 layout->addLayout(lay2); 0089 0090 m_roIndicator = new QLabel; 0091 m_roIndicator->setPixmap(smallIcon(QString::fromLatin1("emblem-readonly"))); 0092 lay2->addWidget(m_roIndicator); 0093 m_selectableIndicator = new QLabel; 0094 m_selectableIndicator->setPixmap(smallIcon(QString::fromLatin1("emblem-checked"))); 0095 lay2->addWidget(m_selectableIndicator); 0096 0097 m_or = new QRadioButton(i18n("or"), this); 0098 m_and = new QRadioButton(i18n("and"), this); 0099 lay2->addWidget(m_or); 0100 lay2->addWidget(m_and); 0101 lay2->addStretch(1); 0102 0103 // Sorting tool button 0104 QButtonGroup *grp = new QButtonGroup(this); 0105 grp->setExclusive(true); 0106 0107 m_alphaTreeSort = new QToolButton; 0108 m_alphaTreeSort->setIcon(smallIcon(QString::fromLatin1("view-list-tree"))); 0109 m_alphaTreeSort->setCheckable(true); 0110 m_alphaTreeSort->setToolTip(i18n("Sort Alphabetically (Tree)")); 0111 grp->addButton(m_alphaTreeSort); 0112 0113 m_alphaFlatSort = new QToolButton; 0114 m_alphaFlatSort->setIcon(smallIcon(QString::fromLatin1("draw-text"))); 0115 m_alphaFlatSort->setCheckable(true); 0116 m_alphaFlatSort->setToolTip(i18n("Sort Alphabetically (Flat)")); 0117 grp->addButton(m_alphaFlatSort); 0118 0119 m_dateSort = new QToolButton; 0120 m_dateSort->setIcon(smallIcon(QString::fromLatin1("x-office-calendar"))); 0121 m_dateSort->setCheckable(true); 0122 m_dateSort->setToolTip(i18n("Sort by date")); 0123 grp->addButton(m_dateSort); 0124 0125 m_showSelectedOnly = new QToolButton; 0126 m_showSelectedOnly->setIcon(smallIcon(QString::fromLatin1("view-filter"))); 0127 m_showSelectedOnly->setCheckable(true); 0128 m_showSelectedOnly->setToolTip(i18n("Show only selected Ctrl+S")); 0129 m_showSelectedOnly->setChecked(ShowSelectionOnlyManager::instance().selectionIsLimited()); 0130 0131 m_alphaTreeSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); 0132 m_alphaFlatSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); 0133 m_dateSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); 0134 connect(m_dateSort, &QToolButton::clicked, this, &ListSelect::slotSortDate); 0135 connect(m_alphaTreeSort, &QToolButton::clicked, this, &ListSelect::slotSortAlphaTree); 0136 connect(m_alphaFlatSort, &QToolButton::clicked, this, &ListSelect::slotSortAlphaFlat); 0137 connect(m_showSelectedOnly, &QToolButton::clicked, &ShowSelectionOnlyManager::instance(), &ShowSelectionOnlyManager::toggle); 0138 0139 lay2->addWidget(m_alphaTreeSort); 0140 lay2->addWidget(m_alphaFlatSort); 0141 lay2->addWidget(m_dateSort); 0142 lay2->addWidget(m_showSelectedOnly); 0143 0144 connectLineEdit(m_lineEdit); 0145 0146 populate(); 0147 0148 connect(Settings::SettingsData::instance(), &Settings::SettingsData::viewSortTypeChanged, 0149 this, &ListSelect::setViewSortType); 0150 connect(Settings::SettingsData::instance(), &Settings::SettingsData::matchTypeChanged, 0151 this, &ListSelect::updateListview); 0152 0153 connect(&ShowSelectionOnlyManager::instance(), &ShowSelectionOnlyManager::limitToSelected, this, &ListSelect::limitToSelection); 0154 connect(&ShowSelectionOnlyManager::instance(), &ShowSelectionOnlyManager::broaden, this, &ListSelect::showAllChildren); 0155 0156 if (category->isSpecialCategory()) { 0157 if (category->type() == DB::Category::TokensCategory) 0158 setEditMode(ListSelectEditMode::Selectable); 0159 else 0160 setEditMode(ListSelectEditMode::ReadOnly); 0161 } 0162 updateLineEditMode(); 0163 } 0164 0165 void AnnotationDialog::ListSelect::slotReturn() 0166 { 0167 if (computedEditMode() == ListSelectEditMode::Editable) { 0168 QString enteredText = m_lineEdit->text().trimmed(); 0169 if (enteredText.isEmpty()) { 0170 return; 0171 } 0172 0173 if (searchForUntaggedImagesTagNeeded()) { 0174 if (enteredText == Settings::SettingsData::instance()->untaggedTag()) { 0175 KMessageBox::information( 0176 this, 0177 i18n("The tag you entered is the tag that is set automatically for newly " 0178 "found, untagged images (cf. <interface>Settings|Configure KPhotoAlbum..." 0179 "|Categories|Untagged Images</interface>). It will not show up here as " 0180 "long as it is selected for this purpose.")); 0181 m_lineEdit->setText(QString()); 0182 return; 0183 } 0184 } 0185 0186 m_category->addItem(enteredText); 0187 rePopulate(); 0188 0189 QList<QTreeWidgetItem *> items = m_treeWidget->findItems(enteredText, Qt::MatchExactly | Qt::MatchRecursive, 0); 0190 if (!items.isEmpty()) { 0191 items.at(0)->setCheckState(0, Qt::Checked); 0192 if (m_positionable) { 0193 Q_EMIT positionableTagSelected(m_category->name(), items.at(0)->text(0)); 0194 } 0195 } else { 0196 Q_ASSERT(false); 0197 } 0198 0199 m_lineEdit->clear(); 0200 } 0201 updateSelectionCount(); 0202 } 0203 0204 void ListSelect::slotExternalReturn(const QString &text) 0205 { 0206 m_lineEdit->setText(text); 0207 slotReturn(); 0208 } 0209 0210 QString AnnotationDialog::ListSelect::category() const 0211 { 0212 return m_category->name(); 0213 } 0214 0215 void AnnotationDialog::ListSelect::setSelection(const StringSet &on, const StringSet &partiallyOn) 0216 { 0217 for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { 0218 if (partiallyOn.contains((*itemIt)->text(0))) 0219 (*itemIt)->setCheckState(0, Qt::PartiallyChecked); 0220 else 0221 (*itemIt)->setCheckState(0, on.contains((*itemIt)->text(0)) ? Qt::Checked : Qt::Unchecked); 0222 } 0223 0224 m_lineEdit->clear(); 0225 updateSelectionCount(); 0226 } 0227 0228 bool AnnotationDialog::ListSelect::isAND() const 0229 { 0230 return m_and->isChecked(); 0231 } 0232 0233 void AnnotationDialog::ListSelect::setMode(UsageMode mode) 0234 { 0235 m_mode = mode; 0236 updateLineEditMode(); 0237 if (mode == SearchMode) { 0238 // "0" below is sorting key which ensures that None is always at top. 0239 CheckDropItem *item = new CheckDropItem(m_treeWidget, DB::ImageDB::NONE(), QString::fromLatin1("0")); 0240 configureItem(item); 0241 m_and->show(); 0242 m_or->show(); 0243 m_or->setChecked(true); 0244 m_showSelectedOnly->hide(); 0245 } else { 0246 m_and->hide(); 0247 m_or->hide(); 0248 m_showSelectedOnly->show(); 0249 } 0250 for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) 0251 configureItem(dynamic_cast<CategoryListView::CheckDropItem *>(*itemIt)); 0252 0253 // ensure that the selection count indicator matches the current mode: 0254 updateSelectionCount(); 0255 } 0256 0257 void ListSelect::setEditMode(ListSelectEditMode mode) 0258 { 0259 m_editMode = mode; 0260 updateLineEditMode(); 0261 } 0262 0263 void AnnotationDialog::ListSelect::setViewSortType(Settings::ViewSortType tp) 0264 { 0265 showAllChildren(); 0266 0267 // set sortType and redisplay with new sortType 0268 QString text = m_lineEdit->text(); 0269 rePopulate(); 0270 m_lineEdit->setText(text); 0271 setMode(m_mode); // generate the ***NONE*** entry if in search mode 0272 0273 m_alphaTreeSort->setChecked(tp == Settings::SortAlphaTree); 0274 m_alphaFlatSort->setChecked(tp == Settings::SortAlphaFlat); 0275 m_dateSort->setChecked(tp == Settings::SortLastUse); 0276 } 0277 0278 QString AnnotationDialog::ListSelect::text() const 0279 { 0280 return m_lineEdit->text(); 0281 } 0282 0283 void AnnotationDialog::ListSelect::setText(const QString &text) 0284 { 0285 m_lineEdit->setText(text); 0286 m_treeWidget->clearSelection(); 0287 } 0288 0289 void AnnotationDialog::ListSelect::itemSelected(QTreeWidgetItem *item) 0290 { 0291 if (!item) { 0292 // click outside any item 0293 return; 0294 } 0295 0296 if (m_mode == SearchMode) { 0297 QString txt = item->text(0); 0298 QString res; 0299 QRegExp regEnd(QString::fromLatin1("\\s*[&|!]\\s*$")); 0300 QRegExp regStart(QString::fromLatin1("^\\s*[&|!]\\s*")); 0301 0302 if (item->checkState(0) == Qt::Checked) { 0303 int matchPos = m_lineEdit->text().indexOf(txt); 0304 if (matchPos != -1) { 0305 return; 0306 } 0307 0308 int index = m_lineEdit->cursorPosition(); 0309 QString start = m_lineEdit->text().left(index); 0310 QString end = m_lineEdit->text().mid(index); 0311 0312 res = start; 0313 if (!start.isEmpty() && !start.contains(regEnd)) { 0314 res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | "); 0315 } 0316 res += txt; 0317 if (!end.isEmpty() && !end.contains(regStart)) { 0318 res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | "); 0319 } 0320 res += end; 0321 } else { 0322 int index = m_lineEdit->text().indexOf(txt); 0323 if (index == -1) 0324 return; 0325 0326 QString start = m_lineEdit->text().left(index); 0327 QString end = m_lineEdit->text().mid(index + txt.length()); 0328 if (start.contains(regEnd)) 0329 start.replace(regEnd, QString::fromLatin1("")); 0330 else 0331 end.replace(regStart, QString::fromLatin1("")); 0332 0333 res = start + end; 0334 } 0335 m_lineEdit->setText(res); 0336 } 0337 0338 else { 0339 if (m_positionable) { 0340 if (item->checkState(0) == Qt::Checked) { 0341 Q_EMIT positionableTagSelected(m_category->name(), item->text(0)); 0342 } else { 0343 Q_EMIT positionableTagDeselected(m_category->name(), item->text(0)); 0344 } 0345 } 0346 0347 m_lineEdit->clear(); 0348 showAllChildren(); 0349 ensureAllInstancesAreStateChanged(item); 0350 } 0351 } 0352 0353 void AnnotationDialog::ListSelect::showContextMenu(const QPoint &pos) 0354 { 0355 QMenu *menu = new QMenu(this); 0356 0357 QTreeWidgetItem *item = m_treeWidget->itemAt(pos); 0358 // click on any item 0359 QString title = i18n("No Item Selected"); 0360 if (item) 0361 title = item->text(0); 0362 0363 QLabel *label = new QLabel(i18n("<b>%1</b>", title), menu); 0364 label->setAlignment(Qt::AlignCenter); 0365 QWidgetAction *action = new QWidgetAction(menu); 0366 action->setDefaultWidget(label); 0367 menu->addAction(action); 0368 0369 QAction *deleteAction = menu->addAction(smallIcon(QString::fromLatin1("edit-delete")), i18n("Delete")); 0370 QAction *renameAction = menu->addAction(i18n("Rename...")); 0371 0372 QLabel *categoryTitle = new QLabel(i18n("<b>Tag Groups</b>"), menu); 0373 categoryTitle->setAlignment(Qt::AlignCenter); 0374 action = new QWidgetAction(menu); 0375 action->setDefaultWidget(categoryTitle); 0376 menu->addAction(action); 0377 0378 // -------------------------------------------------- Add/Remove member group 0379 DB::MemberMap &memberMap = DB::ImageDB::instance()->memberMap(); 0380 QMenu *members = new QMenu(i18n("Tag groups")); 0381 menu->addMenu(members); 0382 QAction *newCategoryAction = nullptr; 0383 if (item) { 0384 QStringList grps = memberMap.groups(m_category->name()); 0385 0386 for (QStringList::ConstIterator it = grps.constBegin(); it != grps.constEnd(); ++it) { 0387 if (!memberMap.canAddMemberToGroup(m_category->name(), *it, item->text(0))) 0388 continue; 0389 QAction *action = members->addAction(*it); 0390 action->setCheckable(true); 0391 action->setChecked((bool)memberMap.members(m_category->name(), *it, false).contains(item->text(0))); 0392 action->setData(*it); 0393 } 0394 0395 if (!grps.isEmpty()) 0396 members->addSeparator(); 0397 newCategoryAction = members->addAction(i18n("Add this tag to a new tag group...")); 0398 } 0399 0400 QAction *newSubcategoryAction = menu->addAction(i18n("Make this tag a tag group and add a tag...")); 0401 0402 // -------------------------------------------------- Take item out of category 0403 QTreeWidgetItem *parent = item ? item->parent() : nullptr; 0404 QAction *takeAction = nullptr; 0405 if (parent) 0406 takeAction = menu->addAction(i18n("Remove from tag group %1", parent->text(0))); 0407 0408 // -------------------------------------------------- sort 0409 QLabel *sortTitle = new QLabel(i18n("<b>Sorting</b>")); 0410 sortTitle->setAlignment(Qt::AlignCenter); 0411 action = new QWidgetAction(menu); 0412 action->setDefaultWidget(sortTitle); 0413 menu->addAction(action); 0414 0415 QAction *usageAction = menu->addAction(i18n("Usage")); 0416 QAction *alphaFlatAction = menu->addAction(i18n("Alphabetical (Flat)")); 0417 QAction *alphaTreeAction = menu->addAction(i18n("Alphabetical (Tree)")); 0418 usageAction->setCheckable(true); 0419 usageAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); 0420 alphaFlatAction->setCheckable(true); 0421 alphaFlatAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); 0422 alphaTreeAction->setCheckable(true); 0423 alphaTreeAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); 0424 0425 if (!item || computedEditMode() != Editable) { 0426 deleteAction->setEnabled(false); 0427 renameAction->setEnabled(false); 0428 members->setEnabled(false); 0429 if (newCategoryAction) 0430 newCategoryAction->setEnabled(false); 0431 newSubcategoryAction->setEnabled(false); 0432 if (takeAction) 0433 takeAction->setEnabled(false); 0434 } 0435 // -------------------------------------------------- exec 0436 QAction *which = menu->exec(m_treeWidget->mapToGlobal(pos)); 0437 if (which == nullptr) 0438 return; 0439 else if (which == deleteAction) { 0440 Q_ASSERT(item); 0441 Q_ASSERT(computedEditMode() == Editable); 0442 int code = KMessageBox::warningContinueCancel(this, i18n("<p>Do you really want to delete \"%1\"?<br/>" 0443 "Deleting the item will remove any information " 0444 "about it from any image containing the item.</p>", 0445 title), 0446 i18n("Really Delete %1?", item->text(0)), 0447 KStandardGuiItem::del()); 0448 if (code == KMessageBox::Continue) { 0449 if (item->checkState(0) == Qt::Checked && m_positionable) { 0450 // An area could be linked against this. We can use positionableTagDeselected 0451 // here, as the procedure is the same as if the tag had been deselected. 0452 Q_EMIT positionableTagDeselected(m_category->name(), item->text(0)); 0453 } 0454 0455 m_category->removeItem(item->text(0)); 0456 rePopulate(); 0457 } 0458 } else if (which == renameAction) { 0459 Q_ASSERT(item); 0460 Q_ASSERT(computedEditMode() == Editable); 0461 bool ok; 0462 QString newStr = QInputDialog::getText(this, 0463 i18nc("@title", "Rename Item"), i18n("Enter new name:"), 0464 QLineEdit::Normal, 0465 item->text(0), &ok) 0466 .trimmed(); 0467 0468 if (ok && !newStr.isEmpty() && newStr != item->text(0)) { 0469 const QString question = i18n("<p>Do you really want to rename \"%1\" to \"%2\"?<br/>" 0470 "Doing so will rename \"%3\" " 0471 "on any image containing it.</p>", 0472 item->text(0), newStr, item->text(0)); 0473 const QString questionTitle = i18nc("@title", "Rename %1?", item->text(0)); 0474 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0475 const auto answer = KMessageBox::questionTwoActions(this, 0476 question, 0477 questionTitle, 0478 KGuiItem(i18nc("@action:button", "Rename")), 0479 KStandardGuiItem::cancel()); 0480 if (answer == KMessageBox::ButtonCode::PrimaryAction) { 0481 #else 0482 const auto answer = KMessageBox::questionYesNo(this, question, questionTitle); 0483 if (answer == KMessageBox::Yes) { 0484 #endif 0485 QString oldStr = item->text(0); 0486 m_category->renameItem(oldStr, newStr); 0487 bool checked = item->checkState(0) == Qt::Checked; 0488 rePopulate(); 0489 // rePopuldate doesn't ask the backend if the item should be checked, so we need to do that. 0490 checkItem(newStr, checked); 0491 0492 // rename the category image too 0493 QString oldFile = m_category->fileForCategoryImage(category(), oldStr); 0494 QString newFile = m_category->fileForCategoryImage(category(), newStr); 0495 KIO::move(QUrl::fromLocalFile(oldFile), QUrl::fromLocalFile(newFile)); 0496 0497 if (m_positionable) { 0498 // Also take care of areas that could be linked against this 0499 Q_EMIT positionableTagRenamed(m_category->name(), oldStr, newStr); 0500 } 0501 } 0502 } 0503 } else if (which == usageAction) { 0504 Settings::SettingsData::instance()->setViewSortType(Settings::SortLastUse); 0505 } else if (which == alphaTreeAction) { 0506 Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaTree); 0507 } else if (which == alphaFlatAction) { 0508 Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaFlat); 0509 } else if (which == newCategoryAction) { 0510 Q_ASSERT(item); 0511 Q_ASSERT(computedEditMode() == Editable); 0512 QString superCategory = QInputDialog::getText(this, 0513 i18n("New tag group"), 0514 i18n("Name for the new tag group the tag will be added to:")); 0515 if (superCategory.isEmpty()) 0516 return; 0517 m_category->addItem(superCategory); 0518 memberMap.addGroup(m_category->name(), superCategory); 0519 memberMap.addMemberToGroup(m_category->name(), superCategory, item->text(0)); 0520 // DB::ImageDB::instance()->setMemberMap( memberMap ); 0521 rePopulate(); 0522 } else if (which == newSubcategoryAction) { 0523 Q_ASSERT(item); 0524 Q_ASSERT(computedEditMode() == Editable); 0525 QString subCategory = QInputDialog::getText(this, 0526 i18n("Add a tag"), 0527 i18n("Name for the tag to be added to this tag group:")); 0528 if (subCategory.isEmpty()) 0529 return; 0530 0531 m_category->addItem(subCategory); 0532 memberMap.addGroup(m_category->name(), item->text(0)); 0533 memberMap.addMemberToGroup(m_category->name(), item->text(0), subCategory); 0534 // DB::ImageDB::instance()->setMemberMap( memberMap ); 0535 m_category->addItem(subCategory); 0536 0537 rePopulate(); 0538 checkItem(subCategory, true); 0539 } else if (which == takeAction) { 0540 Q_ASSERT(item); 0541 Q_ASSERT(computedEditMode() == Editable); 0542 memberMap.removeMemberFromGroup(m_category->name(), parent->text(0), item->text(0)); 0543 rePopulate(); 0544 } else { 0545 Q_ASSERT(item); 0546 Q_ASSERT(computedEditMode() == Editable); 0547 QString checkedItem = which->data().value<QString>(); 0548 if (which->isChecked()) // choosing the item doesn't check it, so this is the value before. 0549 memberMap.addMemberToGroup(m_category->name(), checkedItem, item->text(0)); 0550 else 0551 memberMap.removeMemberFromGroup(m_category->name(), checkedItem, item->text(0)); 0552 rePopulate(); 0553 } 0554 0555 delete menu; 0556 } 0557 0558 void AnnotationDialog::ListSelect::addItems(DB::CategoryItem *item, QTreeWidgetItem *parent) 0559 { 0560 const bool isReadOnly = computedEditMode() == ListSelectEditMode::ReadOnly; 0561 for (QList<DB::CategoryItem *>::ConstIterator subcategoryIt = item->mp_subcategories.constBegin(); subcategoryIt != item->mp_subcategories.constEnd(); ++subcategoryIt) { 0562 CheckDropItem *newItem = nullptr; 0563 0564 if (parent == nullptr) 0565 newItem = new CheckDropItem(m_treeWidget, (*subcategoryIt)->mp_name, QString()); 0566 else 0567 newItem = new CheckDropItem(m_treeWidget, parent, (*subcategoryIt)->mp_name, QString()); 0568 0569 newItem->setExpanded(true); 0570 configureItem(newItem); 0571 if (isReadOnly) { 0572 newItem->setFlags(newItem->flags() ^ Qt::ItemIsUserCheckable); 0573 } 0574 0575 addItems(*subcategoryIt, newItem); 0576 } 0577 } 0578 0579 void AnnotationDialog::ListSelect::populate() 0580 { 0581 m_treeWidget->clear(); 0582 0583 if (Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree) 0584 populateAlphaTree(); 0585 else if (Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat) 0586 populateAlphaFlat(); 0587 else 0588 populateMRU(); 0589 0590 hideUntaggedImagesTag(); 0591 } 0592 0593 bool AnnotationDialog::ListSelect::searchForUntaggedImagesTagNeeded() 0594 { 0595 if (!DB::ImageDB::instance()->untaggedCategoryFeatureConfigured() 0596 || Settings::SettingsData::instance()->untaggedImagesTagVisible()) { 0597 return false; 0598 } 0599 0600 if (Settings::SettingsData::instance()->untaggedCategory() != category()) { 0601 return false; 0602 } 0603 0604 return true; 0605 } 0606 0607 void AnnotationDialog::ListSelect::hideUntaggedImagesTag() 0608 { 0609 if (!searchForUntaggedImagesTagNeeded()) { 0610 return; 0611 } 0612 0613 QTreeWidgetItem *untaggedImagesTag = getUntaggedImagesTag(); 0614 if (untaggedImagesTag) { 0615 untaggedImagesTag->setHidden(true); 0616 } 0617 } 0618 0619 void AnnotationDialog::ListSelect::slotSortDate() 0620 { 0621 Settings::SettingsData::instance()->setViewSortType(Settings::SortLastUse); 0622 } 0623 0624 void AnnotationDialog::ListSelect::slotSortAlphaTree() 0625 { 0626 Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaTree); 0627 } 0628 0629 void AnnotationDialog::ListSelect::slotSortAlphaFlat() 0630 { 0631 Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaFlat); 0632 } 0633 0634 void AnnotationDialog::ListSelect::rePopulate() 0635 { 0636 const StringSet on = itemsOn(); 0637 const StringSet noChange = itemsUnchanged(); 0638 populate(); 0639 setSelection(on, noChange); 0640 0641 if (ShowSelectionOnlyManager::instance().selectionIsLimited()) 0642 limitToSelection(); 0643 } 0644 0645 void AnnotationDialog::ListSelect::showOnlyItemsMatching(const QString &text) 0646 { 0647 ListViewTextMatchHider dummy(text, Settings::SettingsData::instance()->matchType(), m_treeWidget); 0648 ShowSelectionOnlyManager::instance().unlimitFromSelection(); 0649 } 0650 0651 void AnnotationDialog::ListSelect::populateAlphaTree() 0652 { 0653 DB::CategoryItemPtr item = m_category->itemsCategories(); 0654 0655 m_treeWidget->setRootIsDecorated(true); 0656 addItems(item.data(), nullptr); 0657 m_treeWidget->sortByColumn(0, Qt::AscendingOrder); 0658 m_treeWidget->setSortingEnabled(true); 0659 } 0660 0661 void AnnotationDialog::ListSelect::populateAlphaFlat() 0662 { 0663 QStringList items = m_category->itemsInclCategories(); 0664 items.sort(); 0665 0666 m_treeWidget->setRootIsDecorated(false); 0667 for (QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) { 0668 CheckDropItem *item = new CheckDropItem(m_treeWidget, *itemIt, *itemIt); 0669 configureItem(item); 0670 } 0671 m_treeWidget->sortByColumn(1, Qt::AscendingOrder); 0672 m_treeWidget->setSortingEnabled(true); 0673 } 0674 0675 void AnnotationDialog::ListSelect::populateMRU() 0676 { 0677 QStringList items = m_category->itemsInclCategories(); 0678 0679 m_treeWidget->setRootIsDecorated(false); 0680 int index = 100000; // This counter will be converted to a string, and compared, and we don't want "1111" to be less than "2" 0681 for (QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) { 0682 ++index; 0683 CheckDropItem *item = new CheckDropItem(m_treeWidget, *itemIt, QString::number(index)); 0684 configureItem(item); 0685 } 0686 m_treeWidget->sortByColumn(1, Qt::AscendingOrder); 0687 m_treeWidget->setSortingEnabled(true); 0688 } 0689 0690 void AnnotationDialog::ListSelect::toggleSortType() 0691 { 0692 Settings::SettingsData *data = Settings::SettingsData::instance(); 0693 if (data->viewSortType() == Settings::SortLastUse) 0694 data->setViewSortType(Settings::SortAlphaTree); 0695 else if (data->viewSortType() == Settings::SortAlphaTree) 0696 data->setViewSortType(Settings::SortAlphaFlat); 0697 else 0698 data->setViewSortType(Settings::SortLastUse); 0699 } 0700 0701 void AnnotationDialog::ListSelect::updateListview() 0702 { 0703 // update item list (e.g. when MatchType changes): 0704 showOnlyItemsMatching(text()); 0705 } 0706 0707 void AnnotationDialog::ListSelect::limitToSelection() 0708 { 0709 if (computedEditMode() != Editable) 0710 return; 0711 0712 m_showSelectedOnly->setChecked(true); 0713 ListViewCheckedHider dummy(m_treeWidget); 0714 0715 hideUntaggedImagesTag(); 0716 } 0717 0718 void AnnotationDialog::ListSelect::showAllChildren() 0719 { 0720 m_showSelectedOnly->setChecked(false); 0721 showOnlyItemsMatching(QString()); 0722 hideUntaggedImagesTag(); 0723 } 0724 0725 QTreeWidgetItem *AnnotationDialog::ListSelect::getUntaggedImagesTag() 0726 { 0727 QList<QTreeWidgetItem *> matchingTags = m_treeWidget->findItems( 0728 Settings::SettingsData::instance()->untaggedTag(), 0729 Qt::MatchExactly | Qt::MatchRecursive, 0); 0730 0731 // Be sure not to crash here in case the config points to a non-existent tag 0732 if (matchingTags.at(0) == nullptr) { 0733 return nullptr; 0734 } else { 0735 return matchingTags.at(0); 0736 } 0737 } 0738 0739 void ListSelect::updateLineEditMode() 0740 { 0741 if (m_editMode == Selectable) 0742 m_lineEdit->setMode(SearchMode); 0743 else 0744 m_lineEdit->setMode(m_mode); 0745 0746 const bool isReadOnly = computedEditMode() == ListSelectEditMode::ReadOnly; 0747 m_roIndicator->setVisible(isReadOnly); 0748 // deactivate read-only fields when editing: 0749 m_lineEdit->setEnabled((m_mode == UsageMode::SearchMode) || !isReadOnly); 0750 const bool isSelectable = computedEditMode() == ListSelectEditMode::Selectable; 0751 m_selectableIndicator->setVisible(isSelectable); 0752 } 0753 0754 void AnnotationDialog::ListSelect::updateSelectionCount() 0755 { 0756 if (m_baseTitle.isEmpty() /* --> first time */ 0757 || !parentWidget()->windowTitle().startsWith(m_baseTitle) /* --> title has changed */) { 0758 0759 // save the original parentWidget title 0760 m_baseTitle = parentWidget()->windowTitle(); 0761 } 0762 0763 int itemsOnCount = itemsOn().size(); 0764 // Don't count the untagged images tag: 0765 if (searchForUntaggedImagesTagNeeded()) { 0766 QTreeWidgetItem *untaggedImagesTag = getUntaggedImagesTag(); 0767 if (untaggedImagesTag) { 0768 if (untaggedImagesTag->checkState(0) != Qt::Unchecked) { 0769 itemsOnCount--; 0770 } 0771 } 0772 } 0773 0774 switch (m_mode) { 0775 case InputMultiImageConfigMode: 0776 if (itemsUnchanged().size() > 0) { 0777 // if min != max 0778 // tri-state selection -> show min-max (selected items vs. partially selected items): 0779 parentWidget()->setWindowTitle(i18nc( 0780 "Category name, then min-max of selected tags across several images. E.g. 'People (1-2)'", 0781 "%1 (%2-%3)", 0782 m_baseTitle, 0783 itemsOnCount, 0784 itemsOnCount + itemsUnchanged().size())); 0785 break; 0786 } // else fall through and only show one number: 0787 Q_FALLTHROUGH(); 0788 case InputSingleImageConfigMode: 0789 if (itemsOnCount > 0) { 0790 // if any tags have been selected 0791 // "normal" on/off states -> show selected items 0792 parentWidget()->setWindowTitle( 0793 i18nc("Category name, then number of selected tags. E.g. 'People (1)'", 0794 "%1 (%2)", 0795 m_baseTitle, itemsOnCount)); 0796 break; 0797 } // else fall through and only show category 0798 Q_FALLTHROUGH(); 0799 case SearchMode: 0800 // no indicator while searching 0801 parentWidget()->setWindowTitle(m_baseTitle); 0802 break; 0803 } 0804 } 0805 0806 void AnnotationDialog::ListSelect::configureItem(CategoryListView::CheckDropItem *item) 0807 { 0808 bool isDNDAllowed = Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree; 0809 item->setDNDEnabled(isDNDAllowed && !m_category->isSpecialCategory()); 0810 } 0811 0812 ListSelectEditMode ListSelect::computedEditMode() const 0813 { 0814 if (m_mode == SearchMode) 0815 return ListSelectEditMode::Selectable; 0816 return m_editMode; 0817 } 0818 0819 StringSet AnnotationDialog::ListSelect::itemsOn() const 0820 { 0821 return itemsOfState(Qt::Checked); 0822 } 0823 0824 StringSet AnnotationDialog::ListSelect::itemsOff() const 0825 { 0826 return itemsOfState(Qt::Unchecked); 0827 } 0828 0829 StringSet AnnotationDialog::ListSelect::itemsOfState(Qt::CheckState state) const 0830 { 0831 StringSet res; 0832 for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { 0833 if ((*itemIt)->checkState(0) == state) 0834 res.insert((*itemIt)->text(0)); 0835 } 0836 return res; 0837 } 0838 0839 StringSet AnnotationDialog::ListSelect::itemsUnchanged() const 0840 { 0841 return itemsOfState(Qt::PartiallyChecked); 0842 } 0843 0844 void AnnotationDialog::ListSelect::checkItem(const QString itemText, bool b) 0845 { 0846 QList<QTreeWidgetItem *> items = m_treeWidget->findItems(itemText, Qt::MatchExactly | Qt::MatchRecursive); 0847 if (!items.isEmpty()) 0848 items.at(0)->setCheckState(0, b ? Qt::Checked : Qt::Unchecked); 0849 else 0850 Q_ASSERT(false); 0851 } 0852 0853 /** 0854 * An item may be member of a number of categories. Mike may be a member of coworkers and friends. 0855 * Selecting the item in one subcategory, should select him in all. 0856 */ 0857 void AnnotationDialog::ListSelect::ensureAllInstancesAreStateChanged(QTreeWidgetItem *item) 0858 { 0859 const bool on = item->checkState(0) == Qt::Checked; 0860 for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { 0861 if ((*itemIt) != item && (*itemIt)->text(0) == item->text(0)) 0862 (*itemIt)->setCheckState(0, on ? Qt::Checked : Qt::Unchecked); 0863 } 0864 } 0865 0866 QWidget *AnnotationDialog::ListSelect::lineEdit() const 0867 { 0868 return m_lineEdit; 0869 } 0870 0871 void AnnotationDialog::ListSelect::setPositionable(bool positionableState) 0872 { 0873 m_positionable = positionableState; 0874 } 0875 0876 bool AnnotationDialog::ListSelect::positionable() const 0877 { 0878 return m_positionable; 0879 } 0880 0881 bool AnnotationDialog::ListSelect::tagIsChecked(QString tag) const 0882 { 0883 QList<QTreeWidgetItem *> matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); 0884 0885 if (matchingTags.isEmpty()) { 0886 return false; 0887 } 0888 0889 return (bool)matchingTags.first()->checkState(0); 0890 } 0891 0892 /** 0893 * @brief ListSelect::connectLineEdit associates a CompletableLineEdit with this ListSelect 0894 * This method also allows to connect an external CompletableLineEdit to work with this ListSelect. 0895 * @param le 0896 */ 0897 void ListSelect::connectLineEdit(CompletableLineEdit *le) 0898 { 0899 le->setObjectName(m_category->name()); 0900 le->setListView(m_treeWidget); 0901 #if KCOMPLETION_VERSION >= QT_VERSION_CHECK(5, 81, 0) 0902 connect(le, &KLineEdit::returnKeyPressed, this, &ListSelect::slotExternalReturn); 0903 #else 0904 connect(le, &KLineEdit::returnPressed, this, &ListSelect::slotExternalReturn); 0905 #endif 0906 } 0907 0908 void AnnotationDialog::ListSelect::ensureTagIsSelected(QString category, QString tag) 0909 { 0910 if (category != m_lineEdit->objectName()) { 0911 // The selected tag's category does not belong to this ListSelect 0912 return; 0913 } 0914 0915 // Be sure that tag is actually checked 0916 QList<QTreeWidgetItem *> matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); 0917 0918 // If we have the requested category, but not this tag, add it. 0919 // This should only happen if the recognition database is copied from another database 0920 // or has been changed outside of KPA. But this _can_ happen and simply adding a 0921 // missing tag does not hurt ;-) 0922 if (matchingTags.isEmpty()) { 0923 m_category->addItem(tag); 0924 rePopulate(); 0925 // Now, we find it 0926 matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); 0927 } 0928 0929 matchingTags.first()->setCheckState(0, Qt::Checked); 0930 } 0931 0932 void AnnotationDialog::ListSelect::deselectTag(QString tag) 0933 { 0934 QList<QTreeWidgetItem *> matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); 0935 matchingTags.first()->setCheckState(0, Qt::Unchecked); 0936 } 0937 0938 // vi:expandtab:tabstop=4 shiftwidth=4: 0939 0940 #include "moc_ListSelect.cpp"