File indexing completed on 2024-04-28 04:21:19
0001 // SPDX-FileCopyrightText: 2003-2022 The KPhotoAlbum Development Team 0002 // SPDX-FileCopyrightText: 2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0003 // 0004 // SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 #include "CategoryPage.h" 0007 0008 // Qt includes 0009 #include <QCheckBox> 0010 #include <QComboBox> 0011 #include <QGridLayout> 0012 #include <QGroupBox> 0013 #include <QHBoxLayout> 0014 #include <QLabel> 0015 #include <QLocale> 0016 #include <QPushButton> 0017 #include <QSpinBox> 0018 #include <QVBoxLayout> 0019 0020 // KDE includes 0021 #include <KIconButton> 0022 #include <KLocalizedString> 0023 #include <KMessageBox> 0024 #include <kwidgetsaddons_version.h> 0025 0026 // Local includes 0027 #include "CategoryItem.h" 0028 #include "SettingsDialog.h" 0029 #include "UntaggedGroupBox.h" 0030 0031 #include <DB/CategoryCollection.h> 0032 #include <DB/ImageDB.h> 0033 #include <DB/MemberMap.h> 0034 #include <MainWindow/DirtyIndicator.h> 0035 #include <MainWindow/Window.h> 0036 0037 Settings::CategoryPage::CategoryPage(QWidget *parent) 0038 : QWidget(parent) 0039 { 0040 QVBoxLayout *mainLayout = new QVBoxLayout(this); 0041 0042 // The category settings 0043 0044 QGroupBox *categoryGroupBox = new QGroupBox; 0045 mainLayout->addWidget(categoryGroupBox); 0046 categoryGroupBox->setTitle(i18n("Category Settings")); 0047 QHBoxLayout *categoryLayout = new QHBoxLayout(categoryGroupBox); 0048 0049 // Category list 0050 0051 QVBoxLayout *categorySideLayout = new QVBoxLayout; 0052 categoryLayout->addLayout(categorySideLayout); 0053 0054 m_categoriesListWidget = new QListWidget; 0055 0056 connect(m_categoriesListWidget, &QListWidget::itemClicked, this, &CategoryPage::editCategory); 0057 connect(m_categoriesListWidget, &QListWidget::itemSelectionChanged, this, &CategoryPage::editSelectedCategory); 0058 connect(m_categoriesListWidget, &QListWidget::itemChanged, this, &CategoryPage::categoryNameChanged); 0059 0060 categorySideLayout->addWidget(m_categoriesListWidget); 0061 0062 // New, Delete, and buttons 0063 0064 QHBoxLayout *newDeleteRenameLayout = new QHBoxLayout; 0065 categorySideLayout->addLayout(newDeleteRenameLayout); 0066 0067 m_newCategoryButton = new QPushButton(i18n("New")); 0068 connect(m_newCategoryButton, &QPushButton::clicked, this, &CategoryPage::newCategory); 0069 newDeleteRenameLayout->addWidget(m_newCategoryButton); 0070 0071 m_delItem = new QPushButton(i18n("Delete")); 0072 connect(m_delItem, &QPushButton::clicked, this, &CategoryPage::deleteCurrentCategory); 0073 newDeleteRenameLayout->addWidget(m_delItem); 0074 0075 m_renameItem = new QPushButton(i18n("Rename")); 0076 connect(m_renameItem, &QPushButton::clicked, this, &CategoryPage::renameCurrentCategory); 0077 newDeleteRenameLayout->addWidget(m_renameItem); 0078 0079 // Category settings 0080 0081 QVBoxLayout *rightSideLayout = new QVBoxLayout; 0082 categoryLayout->addLayout(rightSideLayout); 0083 0084 // Header 0085 m_categoryLabel = new QLabel; 0086 m_categoryLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); 0087 rightSideLayout->addWidget(m_categoryLabel); 0088 0089 // Pending rename label 0090 m_renameLabel = new QLabel; 0091 m_renameLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); 0092 rightSideLayout->addWidget(m_renameLabel); 0093 QDialog *parentDialog = qobject_cast<QDialog *>(parent); 0094 connect(parentDialog, &QDialog::rejected, m_renameLabel, &QLabel::clear); 0095 0096 // Some space looks better here :-) 0097 QLabel *spacer = new QLabel; 0098 rightSideLayout->addWidget(spacer); 0099 0100 // Here we start with the actual settings 0101 0102 QGridLayout *settingsLayout = new QGridLayout; 0103 rightSideLayout->addLayout(settingsLayout); 0104 0105 int row = 0; 0106 0107 // Positionable 0108 m_positionableLabel = new QLabel(i18n("Positionable tags:")); 0109 settingsLayout->addWidget(m_positionableLabel, row, 0); 0110 m_positionable = new QCheckBox(i18n("Tags in this category can be\n" 0111 "associated with areas within images")); 0112 settingsLayout->addWidget(m_positionable, row, 1); 0113 connect(m_positionable, &QCheckBox::clicked, this, &CategoryPage::positionableChanged); 0114 row++; 0115 0116 // Icon 0117 m_iconLabel = new QLabel(i18n("Icon:")); 0118 settingsLayout->addWidget(m_iconLabel, row, 0); 0119 m_icon = new KIconButton; 0120 settingsLayout->addWidget(m_icon, row, 1); 0121 m_icon->setIconSize(32); 0122 m_icon->setIcon(QString::fromUtf8("personsIcon")); 0123 connect(m_icon, &KIconButton::iconChanged, this, &CategoryPage::iconChanged); 0124 row++; 0125 0126 // Thumbnail size 0127 m_thumbnailSizeInCategoryLabel = new QLabel(i18n("Thumbnail size:")); 0128 settingsLayout->addWidget(m_thumbnailSizeInCategoryLabel, row, 0); 0129 m_thumbnailSizeInCategory = new QSpinBox; 0130 m_thumbnailSizeInCategory->setRange(32, 512); 0131 m_thumbnailSizeInCategory->setSingleStep(32); 0132 settingsLayout->addWidget(m_thumbnailSizeInCategory, row, 1); 0133 connect(m_thumbnailSizeInCategory, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &CategoryPage::thumbnailSizeChanged); 0134 row++; 0135 0136 // Preferred View 0137 m_preferredViewLabel = new QLabel(i18n("Preferred view:")); 0138 settingsLayout->addWidget(m_preferredViewLabel, row, 0); 0139 m_preferredView = new QComboBox; 0140 settingsLayout->addWidget(m_preferredView, row, 1); 0141 m_preferredView->addItems(QStringList() 0142 << i18n("List View") 0143 << i18n("List View with Custom Thumbnails") 0144 << i18n("Icon View") 0145 << i18n("Icon View with Custom Thumbnails")); 0146 connect(m_preferredView, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &CategoryPage::preferredViewChanged); 0147 0148 rightSideLayout->addStretch(); 0149 0150 // Info about the database not being saved 0151 0152 QHBoxLayout *dbNotSavedLayout = new QHBoxLayout; 0153 mainLayout->addLayout(dbNotSavedLayout); 0154 0155 m_dbNotSavedLabel = new QLabel(i18n("<font color='red'>" 0156 "The database has unsaved changes. As long as those are " 0157 "not saved,<br/>the names of categories can't be changed " 0158 "and new ones can't be added." 0159 "</font>")); 0160 m_dbNotSavedLabel->setWordWrap(true); 0161 dbNotSavedLayout->addWidget(m_dbNotSavedLabel); 0162 0163 m_saveDbNowButton = new QPushButton(i18n("Save the DB now")); 0164 m_saveDbNowButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum); 0165 connect(m_saveDbNowButton, &QPushButton::clicked, this, &CategoryPage::saveDbNow); 0166 dbNotSavedLayout->addWidget(m_saveDbNowButton); 0167 0168 resetInterface(); 0169 0170 // Untagged images 0171 m_untaggedBox = new UntaggedGroupBox; 0172 mainLayout->addWidget(m_untaggedBox); 0173 0174 m_currentCategory = nullptr; 0175 0176 m_categoryNamesChanged = false; 0177 } 0178 0179 void Settings::CategoryPage::resetInterface() 0180 { 0181 enableDisable(false); 0182 if (m_categoriesListWidget->currentItem()) 0183 m_categoriesListWidget->currentItem()->setSelected(false); 0184 resetCategoryLabel(); 0185 m_renameLabel->hide(); 0186 } 0187 0188 void Settings::CategoryPage::editSelectedCategory() 0189 { 0190 editCategory(m_categoriesListWidget->currentItem()); 0191 } 0192 0193 void Settings::CategoryPage::editCategory(QListWidgetItem *i) 0194 { 0195 if (i == nullptr) { 0196 return; 0197 } 0198 0199 m_categoryNameBeforeEdit = i->text(); 0200 0201 Settings::CategoryItem *item = static_cast<Settings::CategoryItem *>(i); 0202 m_currentCategory = item; 0203 m_categoryLabel->setText(i18n("Settings for category <b>%1</b>", item->originalName())); 0204 0205 if (m_currentCategory->originalName() != m_categoryNameBeforeEdit) { 0206 m_renameLabel->setText(i18n("<i>Pending change: rename to \"%1\"</i>", m_categoryNameBeforeEdit)); 0207 m_renameLabel->show(); 0208 } else { 0209 m_renameLabel->clear(); 0210 m_renameLabel->hide(); 0211 } 0212 0213 m_positionable->setChecked(item->positionable()); 0214 m_icon->setIcon(item->icon()); 0215 m_thumbnailSizeInCategory->setValue(item->thumbnailSize()); 0216 m_preferredView->setCurrentIndex(static_cast<int>(item->viewType())); 0217 0218 enableDisable(true); 0219 0220 if (item->originalName() 0221 == DB::ImageDB::instance()->categoryCollection()->categoryForSpecial(DB::Category::TokensCategory)->name()) { 0222 0223 m_delItem->setEnabled(false); 0224 m_positionableLabel->setEnabled(false); 0225 m_positionable->setEnabled(false); 0226 m_thumbnailSizeInCategoryLabel->setEnabled(false); 0227 m_thumbnailSizeInCategory->setEnabled(false); 0228 m_preferredViewLabel->setEnabled(false); 0229 m_preferredView->setEnabled(false); 0230 } 0231 } 0232 0233 void Settings::CategoryPage::categoryNameChanged(QListWidgetItem *item) 0234 { 0235 QString newCategoryName = item->text().simplified(); 0236 m_categoriesListWidget->blockSignals(true); 0237 item->setText(QString()); 0238 m_categoriesListWidget->blockSignals(false); 0239 0240 // Now let's check if the new name is valid :-) 0241 0242 // If it's empty, we're done here. The new name can't be empty. 0243 if (newCategoryName.isEmpty()) { 0244 resetCategory(item); 0245 return; 0246 } 0247 0248 // We don't want to have special category names. 0249 // We do have to search both for the localized version and the C locale version, because a user 0250 // could start KPA e. g. with a German locale and create a "Folder" category (which would not 0251 // be caught by i18n("Folder")), and then start KPA with the C locale, which would produce a 0252 // doubled "Folder" category. 0253 if (newCategoryName == i18n("Folder") 0254 || newCategoryName == QString::fromUtf8("Folder") 0255 || newCategoryName == i18n("Media Type") 0256 || newCategoryName == QString::fromUtf8("Media Type")) { 0257 0258 resetCategory(item); 0259 KMessageBox::error(this, 0260 i18n("<p>Can't change the name of category \"%1\" to \"%2\":</p>" 0261 "<p>\"%2\" is a special category name which is reserved and can't " 0262 "be used for a normal category.</p>", 0263 m_currentCategory->text(), newCategoryName), 0264 i18n("Invalid category name")); 0265 return; 0266 } 0267 0268 // Let's see if we already have a category with this name. 0269 if (m_categoriesListWidget->findItems(newCategoryName, Qt::MatchExactly).size() > 0) { 0270 resetCategory(item); 0271 KMessageBox::error(this, 0272 i18n("<p>Can't change the name of category \"%1\" to \"%2\":</p>" 0273 "<p>A category with this name already exists.</p>", 0274 m_currentCategory->text(), newCategoryName), 0275 i18n("Invalid category name")); 0276 return; 0277 } 0278 0279 // Let's see if we have any pending name changes that would cause collisions. 0280 for (int i = 0; i < m_categoriesListWidget->count(); i++) { 0281 Settings::CategoryItem *cat = static_cast<Settings::CategoryItem *>(m_categoriesListWidget->item(i)); 0282 if (cat == m_currentCategory) { 0283 continue; 0284 } 0285 0286 if (newCategoryName == cat->originalName()) { 0287 resetCategory(item); 0288 KMessageBox::error(this, 0289 i18n("<p>Can't change the name of category \"%1\" to \"%2\":</p>" 0290 "<p>There's a pending rename action on the category \"%2\". " 0291 "Please save this change first.</p>", 0292 m_currentCategory->text(), newCategoryName), 0293 i18n("Unsaved pending renaming action")); 0294 return; 0295 } 0296 } 0297 0298 m_categoriesListWidget->blockSignals(true); 0299 item->setText(newCategoryName); 0300 m_categoriesListWidget->blockSignals(false); 0301 0302 Q_EMIT categoryChangesPending(); 0303 m_untaggedBox->categoryRenamed(m_categoryNameBeforeEdit, newCategoryName); 0304 m_currentCategory->setLabel(newCategoryName); 0305 editCategory(m_currentCategory); 0306 0307 m_categoryNamesChanged = true; 0308 } 0309 0310 void Settings::CategoryPage::resetCategory(QListWidgetItem *item) 0311 { 0312 m_categoriesListWidget->blockSignals(true); 0313 item->setText(m_categoryNameBeforeEdit); 0314 m_categoriesListWidget->blockSignals(false); 0315 } 0316 0317 void Settings::CategoryPage::positionableChanged(bool positionable) 0318 { 0319 if (!m_currentCategory) { 0320 return; 0321 } 0322 0323 if (!positionable) { 0324 const QString question = i18n("<p>Do you really want to make \"%1\" " 0325 "non-positionable?</p>" 0326 "<p>All areas linked against this category " 0327 "will be discarded!</p>", 0328 m_currentCategory->text()); 0329 0330 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0331 const auto answer = KMessageBox::questionTwoActions(this, 0332 question, 0333 i18nc("@title", "Confirm Action"), 0334 KStandardGuiItem::discard(), 0335 KStandardGuiItem::cancel()); 0336 if (answer == KMessageBox::ButtonCode::SecondaryAction) { 0337 m_positionable->setCheckState(Qt::Checked); 0338 return; 0339 } 0340 #else 0341 int answer = KMessageBox::questionYesNo(this, question); 0342 if (answer == KMessageBox::No) { 0343 m_positionable->setCheckState(Qt::Checked); 0344 return; 0345 } 0346 #endif 0347 } 0348 0349 m_currentCategory->setPositionable(positionable); 0350 } 0351 0352 void Settings::CategoryPage::iconChanged(const QString &icon) 0353 { 0354 if (m_currentCategory) { 0355 m_currentCategory->setIcon(icon); 0356 } 0357 } 0358 0359 void Settings::CategoryPage::thumbnailSizeChanged(int size) 0360 { 0361 if (m_currentCategory) { 0362 m_currentCategory->setThumbnailSize(size); 0363 } 0364 } 0365 0366 void Settings::CategoryPage::preferredViewChanged(int i) 0367 { 0368 if (m_currentCategory) { 0369 m_currentCategory->setViewType(static_cast<DB::Category::ViewType>(i)); 0370 } 0371 } 0372 0373 void Settings::CategoryPage::newCategory() 0374 { 0375 // Here starts the real function 0376 0377 QString newCategory = i18n("New category"); 0378 QString checkedCategory = newCategory; 0379 int i = 1; 0380 while (m_categoriesListWidget->findItems(checkedCategory, Qt::MatchExactly).size() > 0) { 0381 i++; 0382 checkedCategory = QString::fromUtf8("%1 %2").arg(newCategory).arg(i); 0383 } 0384 0385 m_categoriesListWidget->blockSignals(true); 0386 m_currentCategory = new Settings::CategoryItem(checkedCategory, 0387 QString(), 0388 DB::Category::TreeView, 0389 64, 0390 m_categoriesListWidget); 0391 m_currentCategory->markAsNewCategory(); 0392 Q_EMIT categoryChangesPending(); 0393 m_currentCategory->setLabel(checkedCategory); 0394 m_currentCategory->setSelected(true); 0395 m_categoriesListWidget->blockSignals(false); 0396 0397 m_positionable->setChecked(false); 0398 m_icon->setIcon(QIcon()); 0399 m_thumbnailSizeInCategory->setValue(64); 0400 enableDisable(true); 0401 0402 editCategory(m_currentCategory); 0403 m_categoriesListWidget->editItem(m_currentCategory); 0404 m_untaggedBox->categoryAdded(newCategory); 0405 } 0406 0407 void Settings::CategoryPage::deleteCurrentCategory() 0408 { 0409 const QString question = i18n("<p>Really delete category \"%1\"?</p>", 0410 m_currentCategory->text()); 0411 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0412 const auto answer = KMessageBox::questionTwoActions(this, 0413 question, 0414 i18n("Confirm Action"), 0415 KStandardGuiItem::del(), 0416 KStandardGuiItem::cancel()); 0417 if (answer == KMessageBox::ButtonCode::SecondaryAction) { 0418 return; 0419 } 0420 #else 0421 const auto answer = KMessageBox::questionYesNo(this, question); 0422 if (answer == KMessageBox::No) { 0423 return; 0424 } 0425 #endif 0426 0427 m_untaggedBox->categoryDeleted(m_currentCategory->text()); 0428 m_deletedCategories.append(m_currentCategory); 0429 m_categoriesListWidget->takeItem(m_categoriesListWidget->row(m_currentCategory)); 0430 m_currentCategory = nullptr; 0431 m_positionable->setChecked(false); 0432 m_icon->setIcon(QIcon()); 0433 m_thumbnailSizeInCategory->setValue(64); 0434 enableDisable(false); 0435 resetCategoryLabel(); 0436 0437 editCategory(m_categoriesListWidget->currentItem()); 0438 Q_EMIT categoryChangesPending(); 0439 } 0440 0441 void Settings::CategoryPage::renameCurrentCategory() 0442 { 0443 m_categoriesListWidget->editItem(m_currentCategory); 0444 } 0445 0446 void Settings::CategoryPage::enableDisable(bool b) 0447 { 0448 m_delItem->setEnabled(b); 0449 m_positionableLabel->setEnabled(b); 0450 m_positionable->setEnabled(b); 0451 m_icon->setEnabled(b); 0452 m_iconLabel->setEnabled(b); 0453 m_thumbnailSizeInCategoryLabel->setEnabled(b); 0454 m_thumbnailSizeInCategory->setEnabled(b); 0455 m_preferredViewLabel->setEnabled(b); 0456 m_preferredView->setEnabled(b); 0457 0458 auto signalBlocker = QSignalBlocker(m_categoriesListWidget); 0459 0460 if (MainWindow::Window::theMainWindow()->dbIsDirty()) { 0461 m_dbNotSavedLabel->show(); 0462 m_saveDbNowButton->show(); 0463 m_renameItem->setEnabled(false); 0464 m_newCategoryButton->setEnabled(false); 0465 0466 for (int i = 0; i < m_categoriesListWidget->count(); i++) { 0467 QListWidgetItem *currentItem = m_categoriesListWidget->item(i); 0468 currentItem->setFlags(currentItem->flags() & ~Qt::ItemIsEditable); 0469 } 0470 } else { 0471 m_dbNotSavedLabel->hide(); 0472 m_saveDbNowButton->hide(); 0473 m_renameItem->setEnabled(b); 0474 m_newCategoryButton->setEnabled(true); 0475 0476 for (int i = 0; i < m_categoriesListWidget->count(); i++) { 0477 QListWidgetItem *currentItem = m_categoriesListWidget->item(i); 0478 currentItem->setFlags(currentItem->flags() | Qt::ItemIsEditable); 0479 } 0480 } 0481 } 0482 0483 void Settings::CategoryPage::saveSettings(Settings::SettingsData *opt, DB::MemberMap *memberMap) 0484 { 0485 // Delete items 0486 for (CategoryItem *item : qAsConst(m_deletedCategories)) { 0487 item->removeFromDatabase(); 0488 } 0489 m_deletedCategories.clear(); 0490 0491 // Created or Modified items 0492 for (int i = 0; i < m_categoriesListWidget->count(); ++i) { 0493 CategoryItem *item = static_cast<CategoryItem *>(m_categoriesListWidget->item(i)); 0494 item->submit(memberMap); 0495 } 0496 0497 // FIXME(jzarl): wtf? we need to fix this atrocity... 0498 DB::ImageDB::instance()->memberMap() = *memberMap; 0499 m_untaggedBox->saveSettings(opt); 0500 0501 if (m_categoryNamesChanged) { 0502 // Probably, one or more category names have been edited. Save the database so that 0503 // all thumbnails are referenced with the correct name. 0504 MainWindow::Window::theMainWindow()->slotSave(); 0505 m_categoryNamesChanged = false; 0506 } 0507 } 0508 0509 void Settings::CategoryPage::loadSettings(Settings::SettingsData *opt) 0510 { 0511 auto signalBlocker = QSignalBlocker(m_categoriesListWidget); 0512 m_categoriesListWidget->clear(); 0513 0514 const QList<DB::CategoryPtr> categories = DB::ImageDB::instance()->categoryCollection()->categories(); 0515 for (const DB::CategoryPtr &category : categories) { 0516 if (category->type() == DB::Category::PlainCategory 0517 || category->type() == DB::Category::TokensCategory) { 0518 Settings::CategoryItem *item = new CategoryItem(category->name(), 0519 category->iconName(), 0520 category->viewType(), 0521 category->thumbnailSize(), 0522 m_categoriesListWidget, 0523 category->positionable()); 0524 Q_UNUSED(item) 0525 } 0526 } 0527 0528 m_untaggedBox->loadSettings(opt); 0529 } 0530 0531 void Settings::CategoryPage::resetCategoryLabel() 0532 { 0533 m_categoryLabel->setText(i18n("<i>Choose a category to edit it</i>")); 0534 } 0535 0536 void Settings::CategoryPage::saveDbNow() 0537 { 0538 MainWindow::Window::theMainWindow()->slotSave(); 0539 resetInterface(); 0540 enableDisable(false); 0541 } 0542 0543 void Settings::CategoryPage::resetCategoryNamesChanged() 0544 { 0545 m_categoryNamesChanged = false; 0546 } 0547 0548 // vi:expandtab:tabstop=4 shiftwidth=4: 0549 0550 #include "moc_CategoryPage.cpp"