diff --git a/AnnotationDialog/Dialog.cpp b/AnnotationDialog/Dialog.cpp index 64caf3c2..a3030df7 100644 --- a/AnnotationDialog/Dialog.cpp +++ b/AnnotationDialog/Dialog.cpp @@ -1,1730 +1,1738 @@ /* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Dialog.h" #include "DateEdit.h" #include "DescriptionEdit.h" #include "ImagePreviewWidget.h" #include "ListSelect.h" #include "Logging.h" #include "ResizableFrame.h" #include "ShortCutManager.h" #include "ShowSelectionOnlyManager.h" #include "enums.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KGEOMAP #include #include #include #endif #include #include #include #include #include +namespace +{ +inline QPixmap smallIcon(const QString &iconName) +{ + return QIcon::fromTheme(iconName).pixmap(KIconLoader::StdSizes::SizeSmall); +} +} + using Utilities::StringSet; /** * \class AnnotationDialog::Dialog * \brief QDialog subclass used for tagging images */ AnnotationDialog::Dialog::Dialog(QWidget *parent) : QDialog(parent) , m_ratingChanged(false) , m_conflictText(i18n("(You have differing descriptions on individual images, setting text here will override them all)")) { Utilities::ShowBusyCursor dummy; ShortCutManager shortCutManager; m_actions = new KActionCollection(this); // The widget stack QWidget *mainWidget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(mainWidget); setLayout(layout); layout->addWidget(mainWidget); m_stack = new QStackedWidget(mainWidget); layout->addWidget(m_stack); // The Viewer m_fullScreenPreview = new Viewer::ViewerWidget(Viewer::ViewerWidget::InlineViewer); m_stack->addWidget(m_fullScreenPreview); // The dock widget m_dockWindow = new QMainWindow; m_stack->addWidget(m_dockWindow); m_dockWindow->setDockNestingEnabled(true); // -------------------------------------------------- Dock widgets m_generalDock = createDock(i18n("Label and Dates"), QString::fromLatin1("Label and Dates"), Qt::TopDockWidgetArea, createDateWidget(shortCutManager)); m_previewDock = createDock(i18n("Image Preview"), QString::fromLatin1("Image Preview"), Qt::TopDockWidgetArea, createPreviewWidget()); m_description = new DescriptionEdit(this); m_description->setProperty("WantsFocus", true); m_description->setObjectName(i18n("Description")); m_description->setCheckSpellingEnabled(true); m_description->setTabChangesFocus(true); // this allows tabbing to the next item in the tab order. m_description->setWhatsThis(i18nc("@info:whatsthis", "A descriptive text of the image." "If Use Exif description is enabled under " "Settings|Configure KPhotoAlbum...|General, a description " "embedded in the image Exif information is imported to this field if available.")); m_descriptionDock = createDock(i18n("Description"), QString::fromLatin1("description"), Qt::LeftDockWidgetArea, m_description); shortCutManager.addDock(m_descriptionDock, m_description); connect(m_description, &DescriptionEdit::pageUpDownPressed, this, &Dialog::descriptionPageUpDownPressed); #ifdef HAVE_KGEOMAP // -------------------------------------------------- Map representation m_annotationMapContainer = new QWidget(this); QVBoxLayout *annotationMapContainerLayout = new QVBoxLayout(m_annotationMapContainer); m_annotationMap = new Map::MapView(this); annotationMapContainerLayout->addWidget(m_annotationMap); QHBoxLayout *mapLoadingProgressLayout = new QHBoxLayout(); annotationMapContainerLayout->addLayout(mapLoadingProgressLayout); m_mapLoadingProgress = new QProgressBar(this); mapLoadingProgressLayout->addWidget(m_mapLoadingProgress); m_mapLoadingProgress->hide(); m_cancelMapLoadingButton = new QPushButton(i18n("Cancel")); mapLoadingProgressLayout->addWidget(m_cancelMapLoadingButton); m_cancelMapLoadingButton->hide(); connect(m_cancelMapLoadingButton, &QPushButton::clicked, this, &Dialog::setCancelMapLoading); m_annotationMapContainer->setObjectName(i18n("Map")); m_mapDock = createDock( i18n("Map"), QString::fromLatin1("map"), Qt::LeftDockWidgetArea, m_annotationMapContainer); shortCutManager.addDock(m_mapDock, m_annotationMapContainer); connect(m_mapDock, &QDockWidget::visibilityChanged, this, &Dialog::annotationMapVisibilityChanged); m_mapDock->setWhatsThis(i18nc("@info:whatsthis", "The map widget allows you to view the location of images if GPS coordinates are found in the Exif information.")); #endif // -------------------------------------------------- Categories QList categories = DB::ImageDB::instance()->categoryCollection()->categories(); // Let's first assume we don't have positionable categories m_positionableCategories = false; for (QList::ConstIterator categoryIt = categories.constBegin(); categoryIt != categories.constEnd(); ++categoryIt) { ListSelect *sel = createListSel(*categoryIt); // Create a QMap of all ListSelect instances, so that we can easily // check if a specific (positioned) tag is (still) selected later m_listSelectList[(*categoryIt)->name()] = sel; QDockWidget *dock = createDock((*categoryIt)->name(), (*categoryIt)->name(), Qt::BottomDockWidgetArea, sel); shortCutManager.addDock(dock, sel->lineEdit()); if ((*categoryIt)->isSpecialCategory()) dock->hide(); // Pass the positionable selection to the object sel->setPositionable((*categoryIt)->positionable()); if (sel->positionable()) { connect(sel, &ListSelect::positionableTagSelected, this, &Dialog::positionableTagSelected); connect(sel, &ListSelect::positionableTagDeselected, this, &Dialog::positionableTagDeselected); connect(sel, &ListSelect::positionableTagRenamed, this, &Dialog::positionableTagRenamed); connect(m_preview->preview(), SIGNAL(proposedTagSelected(QString, QString)), sel, SLOT(ensureTagIsSelected(QString, QString))); // We have at least one positionable category m_positionableCategories = true; } } // -------------------------------------------------- The buttons. // don't use default buttons (Ok, Cancel): QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::NoButton); connect(buttonBox, &QDialogButtonBox::accepted, this, &Dialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &Dialog::reject); QHBoxLayout *lay1 = new QHBoxLayout; layout->addLayout(lay1); m_revertBut = new QPushButton(i18n("Revert This Item")); KAcceleratorManager::setNoAccel(m_revertBut); lay1->addWidget(m_revertBut); m_clearBut = new QPushButton(); KGuiItem::assign(m_clearBut, KGuiItem(i18n("Clear Form"), QApplication::isRightToLeft() ? QString::fromLatin1("clear_left") : QString::fromLatin1("locationbar_erase"))); KAcceleratorManager::setNoAccel(m_clearBut); lay1->addWidget(m_clearBut); QPushButton *optionsBut = new QPushButton(i18n("Options...")); KAcceleratorManager::setNoAccel(optionsBut); lay1->addWidget(optionsBut); lay1->addStretch(1); m_okBut = new QPushButton(i18n("&Done")); lay1->addWidget(m_okBut); m_continueLaterBut = new QPushButton(i18n("Continue &Later")); lay1->addWidget(m_continueLaterBut); QPushButton *cancelBut = new QPushButton(); KGuiItem::assign(cancelBut, KStandardGuiItem::cancel()); lay1->addWidget(cancelBut); // It is unfortunately not possible to ask KAcceleratorManager not to setup the OK and cancel keys. shortCutManager.addTaken(i18nc("@action:button", "&Search")); shortCutManager.addTaken(m_okBut->text()); shortCutManager.addTaken(m_continueLaterBut->text()); shortCutManager.addTaken(cancelBut->text()); connect(m_revertBut, &QPushButton::clicked, this, &Dialog::slotRevert); connect(m_okBut, &QPushButton::clicked, this, &Dialog::doneTagging); connect(m_continueLaterBut, &QPushButton::clicked, this, &Dialog::continueLater); connect(cancelBut, &QPushButton::clicked, this, &Dialog::reject); connect(m_clearBut, &QPushButton::clicked, this, &Dialog::slotClear); connect(optionsBut, &QPushButton::clicked, this, &Dialog::slotOptions); connect(m_preview, &ImagePreviewWidget::imageRotated, this, &Dialog::rotate); connect(m_preview, &ImagePreviewWidget::indexChanged, this, &Dialog::slotIndexChanged); connect(m_preview, &ImagePreviewWidget::imageDeleted, this, &Dialog::slotDeleteImage); connect(m_preview, &ImagePreviewWidget::copyPrevClicked, this, &Dialog::slotCopyPrevious); connect(m_preview, &ImagePreviewWidget::areaVisibilityChanged, this, &Dialog::slotShowAreas); connect(m_preview->preview(), SIGNAL(areaCreated(ResizableFrame *)), this, SLOT(slotNewArea(ResizableFrame *))); // Disable so no button accept return (which would break with the line edits) m_revertBut->setAutoDefault(false); m_okBut->setAutoDefault(false); m_continueLaterBut->setAutoDefault(false); cancelBut->setAutoDefault(false); m_clearBut->setAutoDefault(false); optionsBut->setAutoDefault(false); m_dockWindowCleanState = m_dockWindow->saveState(); loadWindowLayout(); m_current = -1; setGeometry(Settings::SettingsData::instance()->windowGeometry(Settings::AnnotationDialog)); setupActions(); shortCutManager.setupShortCuts(); // WARNING layout->addWidget(buttonBox) must be last item in layout layout->addWidget(buttonBox); } QDockWidget *AnnotationDialog::Dialog::createDock(const QString &title, const QString &name, Qt::DockWidgetArea location, QWidget *widget) { QDockWidget *dock = new QDockWidget(title); KAcceleratorManager::setNoAccel(dock); dock->setObjectName(name); dock->setAllowedAreas(Qt::AllDockWidgetAreas); dock->setWidget(widget); m_dockWindow->addDockWidget(location, dock); m_dockWidgets.append(dock); return dock; } QWidget *AnnotationDialog::Dialog::createDateWidget(ShortCutManager &shortCutManager) { QWidget *top = new QWidget; QVBoxLayout *lay2 = new QVBoxLayout(top); // Image Label QHBoxLayout *lay3 = new QHBoxLayout; lay2->addLayout(lay3); QLabel *label = new QLabel(i18n("Label: ")); lay3->addWidget(label); m_imageLabel = new KLineEdit; m_imageLabel->setProperty("WantsFocus", true); m_imageLabel->setObjectName(i18n("Label")); lay3->addWidget(m_imageLabel); shortCutManager.addLabel(label); label->setBuddy(m_imageLabel); // Date QHBoxLayout *lay4 = new QHBoxLayout; lay2->addLayout(lay4); label = new QLabel(i18n("Date: ")); lay4->addWidget(label); m_startDate = new ::AnnotationDialog::DateEdit(true); lay4->addWidget(m_startDate, 1); connect(m_startDate, QOverload::of(&DateEdit::dateChanged), this, &Dialog::slotStartDateChanged); shortCutManager.addLabel(label); label->setBuddy(m_startDate); m_endDateLabel = new QLabel(QString::fromLatin1("-")); lay4->addWidget(m_endDateLabel); m_endDate = new ::AnnotationDialog::DateEdit(false); lay4->addWidget(m_endDate, 1); // Time m_timeLabel = new QLabel(i18n("Time: ")); lay4->addWidget(m_timeLabel); m_time = new QTimeEdit; lay4->addWidget(m_time); m_isFuzzyDate = new QCheckBox(i18n("Use Fuzzy Date")); m_isFuzzyDate->setWhatsThis(i18nc("@info", "In KPhotoAlbum, images can either have an exact date and time" ", or a fuzzy date which happened any time during" " a specified time interval. Images produced by digital cameras" " do normally have an exact date." "If you don't know exactly when a photo was taken" " (e.g. if the photo comes from an analog camera), then you should set" " Use Fuzzy Date.")); m_isFuzzyDate->setToolTip(m_isFuzzyDate->whatsThis()); lay4->addWidget(m_isFuzzyDate); lay4->addStretch(1); connect(m_isFuzzyDate, &QCheckBox::stateChanged, this, &Dialog::slotSetFuzzyDate); QHBoxLayout *lay8 = new QHBoxLayout; lay2->addLayout(lay8); m_megapixelLabel = new QLabel(i18n("Minimum megapixels:")); lay8->addWidget(m_megapixelLabel); m_megapixel = new QSpinBox; m_megapixel->setRange(0, 99); m_megapixel->setSingleStep(1); m_megapixelLabel->setBuddy(m_megapixel); lay8->addWidget(m_megapixel); lay8->addStretch(1); m_max_megapixelLabel = new QLabel(i18n("Maximum megapixels:")); lay8->addWidget(m_max_megapixelLabel); m_max_megapixel = new QSpinBox; m_max_megapixel->setRange(0, 99); m_max_megapixel->setSingleStep(1); m_max_megapixelLabel->setBuddy(m_max_megapixel); lay8->addWidget(m_max_megapixel); lay8->addStretch(1); QHBoxLayout *lay9 = new QHBoxLayout; lay2->addLayout(lay9); label = new QLabel(i18n("Rating:")); lay9->addWidget(label); m_rating = new KRatingWidget; m_rating->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); lay9->addWidget(m_rating, 0, Qt::AlignCenter); connect(m_rating, static_cast(&KRatingWidget::ratingChanged), this, &Dialog::slotRatingChanged); m_ratingSearchLabel = new QLabel(i18n("Rating search mode:")); lay9->addWidget(m_ratingSearchLabel); m_ratingSearchMode = new KComboBox(lay9); m_ratingSearchMode->addItems(QStringList() << i18n("==") << i18n(">=") << i18n("<=") << i18n("!=")); m_ratingSearchLabel->setBuddy(m_ratingSearchMode); lay9->addWidget(m_ratingSearchMode); // File name search pattern QHBoxLayout *lay10 = new QHBoxLayout; lay2->addLayout(lay10); m_imageFilePatternLabel = new QLabel(i18n("File Name Pattern: ")); lay10->addWidget(m_imageFilePatternLabel); m_imageFilePattern = new KLineEdit; m_imageFilePattern->setObjectName(i18n("File Name Pattern")); lay10->addWidget(m_imageFilePattern); shortCutManager.addLabel(m_imageFilePatternLabel); m_imageFilePatternLabel->setBuddy(m_imageFilePattern); m_searchRAW = new QCheckBox(i18n("Search only for RAW files")); lay2->addWidget(m_searchRAW); lay9->addStretch(1); lay2->addStretch(1); return top; } QWidget *AnnotationDialog::Dialog::createPreviewWidget() { m_preview = new ImagePreviewWidget(m_actions); connect(m_preview, &ImagePreviewWidget::togglePreview, this, &Dialog::togglePreview); return m_preview; } void AnnotationDialog::Dialog::slotRevert() { if (m_setup == InputSingleImageConfigMode) load(); } void AnnotationDialog::Dialog::slotIndexChanged(int index) { if (m_setup != InputSingleImageConfigMode) return; if (m_current >= 0) writeToInfo(); m_current = index; load(); } void AnnotationDialog::Dialog::doneTagging() { saveAndClose(); if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()) { for (DB::ImageInfoListIterator it = m_origList.begin(); it != m_origList.end(); ++it) { (*it)->removeCategoryInfo(Settings::SettingsData::instance()->untaggedCategory(), Settings::SettingsData::instance()->untaggedTag()); } } } /* * Copy tags (only tags/categories, not description/label/...) from previous image to the currently showed one */ void AnnotationDialog::Dialog::slotCopyPrevious() { if (m_setup != InputSingleImageConfigMode) return; if (m_current < 1) return; // FIXME: it would be better to compute the "previous image" in a better way, but let's stick with this for now... DB::ImageInfo &old_info = m_editList[m_current - 1]; m_positionableTagCandidates.clear(); m_lastSelectedPositionableTag.first = QString(); m_lastSelectedPositionableTag.second = QString(); Q_FOREACH (ListSelect *ls, m_optionList) { ls->setSelection(old_info.itemsOfCategory(ls->category())); // Also set all positionable tag candidates if (ls->positionable()) { QString category = ls->category(); QSet selectedTags = old_info.itemsOfCategory(category); QSet positionedTagSet = positionedTags(category); // Add the tag to the positionable candiate list, if no area is already associated with it Q_FOREACH (const auto &tag, selectedTags) { if (!positionedTagSet.contains(tag)) { addTagToCandidateList(category, tag); } } // Check all areas for a linked tag in this category that is probably not selected anymore for (ResizableFrame *area : areas()) { QPair tagData = area->tagData(); if (tagData.first == category) { if (!selectedTags.contains(tagData.second)) { // The linked tag is not selected anymore, so remove it area->removeTagData(); } } } } } } void AnnotationDialog::Dialog::load() { // Remove all areas tidyAreas(); // No areas have been changed m_areasChanged = false; // Empty the positionable tag candidate list and the last selected positionable tag m_positionableTagCandidates.clear(); m_lastSelectedPositionableTag = QPair(); DB::ImageInfo &info = m_editList[m_current]; m_startDate->setDate(info.date().start().date()); if (info.date().hasValidTime()) { m_time->show(); m_time->setTime(info.date().start().time()); m_isFuzzyDate->setChecked(false); } else { m_time->hide(); m_isFuzzyDate->setChecked(true); } if (info.date().start().date() == info.date().end().date()) m_endDate->setDate(QDate()); else m_endDate->setDate(info.date().end().date()); m_imageLabel->setText(info.label()); m_description->setPlainText(info.description()); if (m_setup == InputSingleImageConfigMode) m_rating->setRating(qMax(static_cast(0), info.rating())); m_ratingChanged = false; // A category areas have been linked against could have been deleted // or un-marked as positionable in the meantime, so ... QMap categoryIsPositionable; QList positionableCategories; Q_FOREACH (ListSelect *ls, m_optionList) { ls->setSelection(info.itemsOfCategory(ls->category())); ls->rePopulate(); // Get all selected positionable tags and add them to the candidate list if (ls->positionable()) { QSet selectedTags = ls->itemsOn(); Q_FOREACH (const QString &tagName, selectedTags) { addTagToCandidateList(ls->category(), tagName); } } // ... create a list of all categories and their positionability ... categoryIsPositionable[ls->category()] = ls->positionable(); if (ls->positionable()) { positionableCategories << ls->category(); } } // Create all tagged areas QMap> taggedAreas = info.taggedAreas(); QMapIterator> areasInCategory(taggedAreas); while (areasInCategory.hasNext()) { areasInCategory.next(); QString category = areasInCategory.key(); // ... and check if the respective category is actually there yet and still positionable // (operator[] will insert an empty item if the category has been deleted // and is thus missing in the QMap, but the respective key won't be true) if (categoryIsPositionable[category]) { QMapIterator areaData(areasInCategory.value()); while (areaData.hasNext()) { areaData.next(); QString tag = areaData.key(); // Be sure that the corresponding tag is still checked. The category could have // been un-marked as positionable in the meantime and the tag could have been // deselected, without triggering positionableTagDeselected and the area thus // still remaining. If the category is then re-marked as positionable, the area would // show up without the tag being selected. if (m_listSelectList[category]->tagIsChecked(tag)) { m_preview->preview()->createTaggedArea(category, tag, areaData.value(), m_preview->showAreas()); } } } } if (m_setup == InputSingleImageConfigMode) { setWindowTitle(i18nc("@title:window image %1 of %2 images", "Annotations (%1/%2)", m_current + 1, m_origList.count())); m_preview->canCreateAreas( m_setup == InputSingleImageConfigMode && !info.isVideo() && m_positionableCategories); #ifdef HAVE_KGEOMAP updateMapForCurrentImage(); #endif } m_preview->updatePositionableCategories(positionableCategories); } void AnnotationDialog::Dialog::writeToInfo() { Q_FOREACH (ListSelect *ls, m_optionList) { ls->slotReturn(); } DB::ImageInfo &info = m_editList[m_current]; if (!info.size().isValid()) { // The actual image size has been fetched by ImagePreview, so we can add it to // the database silenty, so that it's saved if the database will be saved. info.setSize(m_preview->preview()->getActualImageSize()); } if (m_time->isHidden()) { if (m_endDate->date().isValid()) info.setDate(DB::ImageDate(QDateTime(m_startDate->date(), QTime(0, 0, 0)), QDateTime(m_endDate->date(), QTime(23, 59, 59)))); else info.setDate(DB::ImageDate(QDateTime(m_startDate->date(), QTime(0, 0, 0)), QDateTime(m_startDate->date(), QTime(23, 59, 59)))); } else info.setDate(DB::ImageDate(QDateTime(m_startDate->date(), m_time->time()))); // Generate a list of all tagged areas QMap> areas = taggedAreas(); info.setLabel(m_imageLabel->text()); info.setDescription(m_description->toPlainText()); Q_FOREACH (ListSelect *ls, m_optionList) { info.setCategoryInfo(ls->category(), ls->itemsOn()); if (ls->positionable()) { info.setPositionedTags(ls->category(), areas[ls->category()]); } } if (m_ratingChanged) { info.setRating(m_rating->rating()); m_ratingChanged = false; } } void AnnotationDialog::Dialog::ShowHideSearch(bool show) { m_megapixel->setVisible(show); m_megapixelLabel->setVisible(show); m_max_megapixel->setVisible(show); m_max_megapixelLabel->setVisible(show); m_searchRAW->setVisible(show); m_imageFilePatternLabel->setVisible(show); m_imageFilePattern->setVisible(show); m_isFuzzyDate->setChecked(show); m_isFuzzyDate->setVisible(!show); slotSetFuzzyDate(); m_ratingSearchMode->setVisible(show); m_ratingSearchLabel->setVisible(show); } QList AnnotationDialog::Dialog::areas() const { return m_preview->preview()->findChildren(); } QMap> AnnotationDialog::Dialog::taggedAreas() const { QMap> taggedAreas; foreach (ResizableFrame *area, areas()) { QPair tagData = area->tagData(); if (!tagData.first.isEmpty()) { taggedAreas[tagData.first][tagData.second] = area->actualCoordinates(); } } return taggedAreas; } int AnnotationDialog::Dialog::configure(DB::ImageInfoList list, bool oneAtATime) { ShowHideSearch(false); if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured()) { DB::ImageDB::instance()->categoryCollection()->categoryForName(Settings::SettingsData::instance()->untaggedCategory())->addItem(Settings::SettingsData::instance()->untaggedTag()); } if (oneAtATime) { m_setup = InputSingleImageConfigMode; } else { m_setup = InputMultiImageConfigMode; // Hide the default positionable category selector m_preview->updatePositionableCategories(); } #ifdef HAVE_KGEOMAP m_mapIsPopulated = false; m_annotationMap->clear(); #endif m_origList = list; m_editList.clear(); for (DB::ImageInfoListConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { m_editList.append(*(*it)); } setup(); if (oneAtATime) { m_current = 0; m_preview->configure(&m_editList, true); load(); } else { m_preview->configure(&m_editList, false); m_preview->canCreateAreas(false); m_startDate->setDate(QDate()); m_endDate->setDate(QDate()); m_time->hide(); m_rating->setRating(0); m_ratingChanged = false; m_areasChanged = false; Q_FOREACH (ListSelect *ls, m_optionList) { setUpCategoryListBoxForMultiImageSelection(ls, list); } m_imageLabel->setText(QString()); m_imageFilePattern->setText(QString()); m_firstDescription = m_editList[0].description(); const bool allTextEqual = std::all_of(m_editList.begin(), m_editList.end(), [=](const DB::ImageInfo &item) -> bool { return item.description() == m_firstDescription; }); if (!allTextEqual) m_firstDescription = m_conflictText; m_description->setPlainText(m_firstDescription); } showHelpDialog(oneAtATime ? InputSingleImageConfigMode : InputMultiImageConfigMode); return exec(); } DB::ImageSearchInfo AnnotationDialog::Dialog::search(DB::ImageSearchInfo *search) { ShowHideSearch(true); #ifdef HAVE_KGEOMAP m_mapIsPopulated = false; m_annotationMap->clear(); #endif m_setup = SearchMode; if (search) m_oldSearch = *search; setup(); m_preview->setImage(QStandardPaths::locate(QStandardPaths::DataLocation, QString::fromLatin1("pics/search.jpg"))); m_ratingChanged = false; showHelpDialog(SearchMode); int ok = exec(); if (ok == QDialog::Accepted) { const QDate start = m_startDate->date(); const QDate end = m_endDate->date(); m_oldSearch = DB::ImageSearchInfo(DB::ImageDate(start, end), m_imageLabel->text(), m_description->toPlainText(), m_imageFilePattern->text()); Q_FOREACH (const ListSelect *ls, m_optionList) { m_oldSearch.setCategoryMatchText(ls->category(), ls->text()); } //FIXME: for the user to search for 0-rated images, he must first change the rating to anything > 0 //then change back to 0 . if (m_ratingChanged) m_oldSearch.setRating(m_rating->rating()); m_ratingChanged = false; m_oldSearch.setSearchMode(m_ratingSearchMode->currentIndex()); m_oldSearch.setMegaPixel(m_megapixel->value()); m_oldSearch.setMaxMegaPixel(m_max_megapixel->value()); m_oldSearch.setSearchRAW(m_searchRAW->isChecked()); #ifdef HAVE_KGEOMAP const KGeoMap::GeoCoordinates::Pair regionSelection = m_annotationMap->getRegionSelection(); m_oldSearch.setRegionSelection(regionSelection); #endif return m_oldSearch; } else return DB::ImageSearchInfo(); } void AnnotationDialog::Dialog::setup() { // Repopulate the listboxes in case data has changed // An group might for example have been renamed. Q_FOREACH (ListSelect *ls, m_optionList) { ls->populate(); } if (m_setup == SearchMode) { KGuiItem::assign(m_okBut, KGuiItem(i18nc("@action:button", "&Search"), QString::fromLatin1("find"))); m_continueLaterBut->hide(); m_revertBut->hide(); m_clearBut->show(); m_preview->setSearchMode(true); setWindowTitle(i18nc("@title:window title of the 'find images' window", "Search")); loadInfo(m_oldSearch); } else { m_okBut->setText(i18n("Done")); m_continueLaterBut->show(); m_revertBut->setEnabled(m_setup == InputSingleImageConfigMode); m_clearBut->hide(); m_revertBut->show(); m_preview->setSearchMode(false); m_preview->setToggleFullscreenPreviewEnabled(m_setup == InputSingleImageConfigMode); setWindowTitle(i18nc("@title:window", "Annotations")); } Q_FOREACH (ListSelect *ls, m_optionList) { ls->setMode(m_setup); } } void AnnotationDialog::Dialog::slotClear() { loadInfo(DB::ImageSearchInfo()); } void AnnotationDialog::Dialog::loadInfo(const DB::ImageSearchInfo &info) { m_startDate->setDate(info.date().start().date()); m_endDate->setDate(info.date().end().date()); Q_FOREACH (ListSelect *ls, m_optionList) { ls->setText(info.categoryMatchText(ls->category())); } m_imageLabel->setText(info.label()); m_description->setText(info.description()); } void AnnotationDialog::Dialog::slotOptions() { // create menu entries for dock windows QMenu *menu = new QMenu(this); QMenu *dockMenu = m_dockWindow->createPopupMenu(); menu->addMenu(dockMenu) ->setText(i18n("Configure Window Layout...")); QAction *saveCurrent = dockMenu->addAction(i18n("Save Current Window Setup")); QAction *reset = dockMenu->addAction(i18n("Reset layout")); // create SortType entries menu->addSeparator(); QActionGroup *sortTypes = new QActionGroup(menu); QAction *alphaTreeSort = new QAction( - SmallIcon(QString::fromLatin1("view-list-tree")), + smallIcon(QString::fromLatin1("view-list-tree")), i18n("Sort Alphabetically (Tree)"), sortTypes); QAction *alphaFlatSort = new QAction( - SmallIcon(QString::fromLatin1("draw-text")), + smallIcon(QString::fromLatin1("draw-text")), i18n("Sort Alphabetically (Flat)"), sortTypes); QAction *dateSort = new QAction( - SmallIcon(QString::fromLatin1("x-office-calendar")), + smallIcon(QString::fromLatin1("x-office-calendar")), i18n("Sort by Date"), sortTypes); alphaTreeSort->setCheckable(true); alphaFlatSort->setCheckable(true); dateSort->setCheckable(true); alphaTreeSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); alphaFlatSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); dateSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); menu->addActions(sortTypes->actions()); connect(dateSort, SIGNAL(triggered()), m_optionList.at(0), SLOT(slotSortDate())); connect(alphaTreeSort, SIGNAL(triggered()), m_optionList.at(0), SLOT(slotSortAlphaTree())); connect(alphaFlatSort, SIGNAL(triggered()), m_optionList.at(0), SLOT(slotSortAlphaFlat())); // create MatchType entries menu->addSeparator(); QActionGroup *matchTypes = new QActionGroup(menu); QAction *matchFromBeginning = new QAction(i18n("Match Tags from the First Character"), matchTypes); QAction *matchFromWordStart = new QAction(i18n("Match Tags from Word Boundaries"), matchTypes); QAction *matchAnywhere = new QAction(i18n("Match Tags Anywhere"), matchTypes); matchFromBeginning->setCheckable(true); matchFromWordStart->setCheckable(true); matchAnywhere->setCheckable(true); // TODO add StatusTip text? // set current state: matchFromBeginning->setChecked(Settings::SettingsData::instance()->matchType() == AnnotationDialog::MatchFromBeginning); matchFromWordStart->setChecked(Settings::SettingsData::instance()->matchType() == AnnotationDialog::MatchFromWordStart); matchAnywhere->setChecked(Settings::SettingsData::instance()->matchType() == AnnotationDialog::MatchAnywhere); // add MatchType actions to menu: menu->addActions(matchTypes->actions()); // create toggle-show-selected entry# if (m_setup != SearchMode) { menu->addSeparator(); QAction *showSelectedOnly = new QAction( - SmallIcon(QString::fromLatin1("view-filter")), + smallIcon(QString::fromLatin1("view-filter")), i18n("Show Only Selected Ctrl+S"), menu); showSelectedOnly->setCheckable(true); showSelectedOnly->setChecked(ShowSelectionOnlyManager::instance().selectionIsLimited()); menu->addAction(showSelectedOnly); connect(showSelectedOnly, SIGNAL(triggered()), &ShowSelectionOnlyManager::instance(), SLOT(toggle())); } // execute menu & handle response: QAction *res = menu->exec(QCursor::pos()); if (res == saveCurrent) slotSaveWindowSetup(); else if (res == reset) slotResetLayout(); else if (res == matchFromBeginning) Settings::SettingsData::instance()->setMatchType(AnnotationDialog::MatchFromBeginning); else if (res == matchFromWordStart) Settings::SettingsData::instance()->setMatchType(AnnotationDialog::MatchFromWordStart); else if (res == matchAnywhere) Settings::SettingsData::instance()->setMatchType(AnnotationDialog::MatchAnywhere); } int AnnotationDialog::Dialog::exec() { m_stack->setCurrentWidget(m_dockWindow); showTornOfWindows(); this->setFocus(); // Set temporary focus before show() is called so that extra cursor is not shown on any "random" input widget show(); // We need to call show before we call setupFocus() otherwise the widget will not yet all have been moved in place. setupFocus(); const int ret = QDialog::exec(); hideTornOfWindows(); return ret; } void AnnotationDialog::Dialog::slotSaveWindowSetup() { const QByteArray data = m_dockWindow->saveState(); QFile file(QString::fromLatin1("%1/layout.dat").arg(Settings::SettingsData::instance()->imageDirectory())); if (!file.open(QIODevice::WriteOnly)) { KMessageBox::sorry(this, i18n("

Could not save the window layout.

" "File %1 could not be opened because of the following error: %2", file.fileName(), file.errorString())); } else if (!(file.write(data) && file.flush())) { KMessageBox::sorry(this, i18n("

Could not save the window layout.

" "File %1 could not be written because of the following error: %2", file.fileName(), file.errorString())); } file.close(); } void AnnotationDialog::Dialog::closeEvent(QCloseEvent *e) { e->ignore(); reject(); } void AnnotationDialog::Dialog::hideTornOfWindows() { for (QDockWidget *dock : m_dockWidgets) { if (dock->isFloating()) { qCDebug(AnnotationDialogLog) << "Hiding dock: " << dock->objectName(); dock->hide(); } } } void AnnotationDialog::Dialog::showTornOfWindows() { for (QDockWidget *dock : m_dockWidgets) { if (dock->isFloating()) { qCDebug(AnnotationDialogLog) << "Showing dock: " << dock->objectName(); dock->show(); } } } AnnotationDialog::ListSelect *AnnotationDialog::Dialog::createListSel(const DB::CategoryPtr &category) { ListSelect *sel = new ListSelect(category, m_dockWindow); m_optionList.append(sel); connect(DB::ImageDB::instance()->categoryCollection(), SIGNAL(itemRemoved(DB::Category *, QString)), this, SLOT(slotDeleteOption(DB::Category *, QString))); connect(DB::ImageDB::instance()->categoryCollection(), SIGNAL(itemRenamed(DB::Category *, QString, QString)), this, SLOT(slotRenameOption(DB::Category *, QString, QString))); return sel; } void AnnotationDialog::Dialog::slotDeleteOption(DB::Category *category, const QString &value) { for (QList::Iterator it = m_editList.begin(); it != m_editList.end(); ++it) { (*it).removeCategoryInfo(category->name(), value); } } void AnnotationDialog::Dialog::slotRenameOption(DB::Category *category, const QString &oldValue, const QString &newValue) { for (QList::Iterator it = m_editList.begin(); it != m_editList.end(); ++it) { (*it).renameItem(category->name(), oldValue, newValue); } } void AnnotationDialog::Dialog::reject() { if (m_stack->currentWidget() == m_fullScreenPreview) { togglePreview(); return; } m_fullScreenPreview->stopPlayback(); if (hasChanges()) { int code = KMessageBox::questionYesNo(this, i18n("

Some changes are made to annotations. Do you really want to cancel all recent changes for each affected file?

")); if (code == KMessageBox::No) return; } closeDialog(); } void AnnotationDialog::Dialog::closeDialog() { tidyAreas(); m_accept = QDialog::Rejected; QDialog::reject(); } bool AnnotationDialog::Dialog::hasChanges() { bool changed = false; if (m_setup == InputSingleImageConfigMode) { writeToInfo(); for (int i = 0; i < m_editList.count(); ++i) { changed |= (*(m_origList[i]) != m_editList[i]); } changed |= m_areasChanged; } else if (m_setup == InputMultiImageConfigMode) { changed |= (!m_startDate->date().isNull()); changed |= (!m_endDate->date().isNull()); Q_FOREACH (ListSelect *ls, m_optionList) { StringSet on, partialOn; std::tie(on, partialOn) = selectionForMultiSelect(ls, m_origList); changed |= (on != ls->itemsOn()); changed |= (partialOn != ls->itemsUnchanged()); } changed |= (!m_imageLabel->text().isEmpty()); changed |= (m_description->toPlainText() != m_firstDescription); changed |= m_ratingChanged; } return changed; } void AnnotationDialog::Dialog::rotate(int angle) { if (m_setup == InputMultiImageConfigMode) { // In doneTagging the preview will be queried for its angle. } else { DB::ImageInfo &info = m_editList[m_current]; info.rotate(angle, DB::RotateImageInfoOnly); emit imageRotated(info.fileName()); } } void AnnotationDialog::Dialog::slotSetFuzzyDate() { if (m_isFuzzyDate->isChecked()) { m_time->hide(); m_timeLabel->hide(); m_endDate->show(); m_endDateLabel->show(); } else { m_time->show(); m_timeLabel->show(); m_endDate->hide(); m_endDateLabel->hide(); } } void AnnotationDialog::Dialog::slotDeleteImage() { // CTRL+Del is a common key combination when editing text // TODO: The word right of cursor should be deleted as expected also in date and category fields if (m_setup == SearchMode) return; if (m_setup == InputMultiImageConfigMode) //TODO: probably delete here should mean remove from selection return; DB::ImageInfoPtr info = m_origList[m_current]; m_origList.remove(info); m_editList.removeAll(m_editList.at(m_current)); MainWindow::DirtyIndicator::markDirty(); if (m_origList.count() == 0) { doneTagging(); return; } if (m_current == (int)m_origList.count()) // we deleted the last image m_current--; load(); } void AnnotationDialog::Dialog::showHelpDialog(UsageMode type) { QString doNotShowKey; QString txt; if (type == SearchMode) { doNotShowKey = QString::fromLatin1("image_config_search_show_help"); txt = i18n("

You have just opened the advanced search dialog; to get the most out of it, " "it is suggested that you read the section in the manual on " "advanced searching.

" "

This dialog is also used for typing in information about images; you can find " "extra tips on its usage by reading about " "typing in.

"); } else { doNotShowKey = QString::fromLatin1("image_config_typein_show_help"); txt = i18n("

You have just opened one of the most important windows in KPhotoAlbum; " "it contains lots of functionality which has been optimized for fast usage.

" "

It is strongly recommended that you take 5 minutes to read the " "documentation for this " "dialog

"); } KMessageBox::information(this, txt, QString(), doNotShowKey, KMessageBox::AllowLink); } void AnnotationDialog::Dialog::resizeEvent(QResizeEvent *) { Settings::SettingsData::instance()->setWindowGeometry(Settings::AnnotationDialog, geometry()); } void AnnotationDialog::Dialog::moveEvent(QMoveEvent *) { Settings::SettingsData::instance()->setWindowGeometry(Settings::AnnotationDialog, geometry()); } void AnnotationDialog::Dialog::setupFocus() { QList list = findChildren(); QList orderedList; // Iterate through all widgets in our dialog. for (QObject *obj : list) { QWidget *current = static_cast(obj); if (!current->property("WantsFocus").isValid() || !current->isVisible()) continue; int cx = current->mapToGlobal(QPoint(0, 0)).x(); int cy = current->mapToGlobal(QPoint(0, 0)).y(); bool inserted = false; // Iterate through the ordered list of widgets, and insert the current one, so it is in the right position in the tab chain. for (QList::iterator orderedIt = orderedList.begin(); orderedIt != orderedList.end(); ++orderedIt) { const QWidget *w = *orderedIt; int wx = w->mapToGlobal(QPoint(0, 0)).x(); int wy = w->mapToGlobal(QPoint(0, 0)).y(); if (wy > cy || (wy == cy && wx >= cx)) { orderedList.insert(orderedIt, current); inserted = true; break; } } if (!inserted) orderedList.append(current); } // now setup tab order. QWidget *prev = nullptr; QWidget *first = nullptr; Q_FOREACH (QWidget *widget, orderedList) { if (prev) { setTabOrder(prev, widget); } else { first = widget; } prev = widget; } if (first) { setTabOrder(prev, first); } // Finally set focus on the first list select Q_FOREACH (QWidget *widget, orderedList) { if (widget->property("FocusCandidate").isValid() && widget->isVisible()) { widget->setFocus(); break; } } } void AnnotationDialog::Dialog::slotResetLayout() { m_dockWindow->restoreState(m_dockWindowCleanState); } void AnnotationDialog::Dialog::slotStartDateChanged(const DB::ImageDate &date) { if (date.start() == date.end()) m_endDate->setDate(QDate()); else m_endDate->setDate(date.end().date()); } void AnnotationDialog::Dialog::loadWindowLayout() { QString fileName = QString::fromLatin1("%1/layout.dat").arg(Settings::SettingsData::instance()->imageDirectory()); if (!QFileInfo(fileName).exists()) { // create default layout // label/date/rating in a visual block with description: m_dockWindow->splitDockWidget(m_generalDock, m_descriptionDock, Qt::Vertical); // more space for description: m_dockWindow->resizeDocks({ m_generalDock, m_descriptionDock }, { 60, 100 }, Qt::Vertical); // more space for preview: m_dockWindow->resizeDocks({ m_generalDock, m_descriptionDock, m_previewDock }, { 200, 200, 800 }, Qt::Horizontal); #ifdef HAVE_KGEOMAP // group the map with the preview m_dockWindow->tabifyDockWidget(m_previewDock, m_mapDock); // make sure the preview tab is active: m_previewDock->raise(); #endif return; } QFile file(fileName); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); m_dockWindow->restoreState(data); } void AnnotationDialog::Dialog::setupActions() { QAction *action = nullptr; action = m_actions->addAction(QString::fromLatin1("annotationdialog-sort-alphatree"), m_optionList.at(0), SLOT(slotSortAlphaTree())); action->setText(i18n("Sort Alphabetically (Tree)")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_F4); action = m_actions->addAction(QString::fromLatin1("annotationdialog-sort-alphaflat"), m_optionList.at(0), SLOT(slotSortAlphaFlat())); action->setText(i18n("Sort Alphabetically (Flat)")); action = m_actions->addAction(QString::fromLatin1("annotationdialog-sort-MRU"), m_optionList.at(0), SLOT(slotSortDate())); action->setText(i18n("Sort Most Recently Used")); action = m_actions->addAction(QString::fromLatin1("annotationdialog-toggle-sort"), m_optionList.at(0), SLOT(toggleSortType())); action->setText(i18n("Toggle Sorting")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_T); action = m_actions->addAction(QString::fromLatin1("annotationdialog-toggle-showing-selected-only"), &ShowSelectionOnlyManager::instance(), SLOT(toggle())); action->setText(i18n("Toggle Showing Selected Items Only")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_S); action = m_actions->addAction(QString::fromLatin1("annotationdialog-next-image"), m_preview, SLOT(slotNext())); action->setText(i18n("Annotate Next")); m_actions->setDefaultShortcut(action, Qt::Key_PageDown); action = m_actions->addAction(QString::fromLatin1("annotationdialog-prev-image"), m_preview, SLOT(slotPrev())); action->setText(i18n("Annotate Previous")); m_actions->setDefaultShortcut(action, Qt::Key_PageUp); action = m_actions->addAction(QString::fromLatin1("annotationdialog-OK-dialog"), this, SLOT(doneTagging())); action->setText(i18n("OK dialog")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Return); action = m_actions->addAction(QString::fromLatin1("annotationdialog-delete-image"), this, SLOT(slotDeleteImage())); action->setText(i18n("Delete")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Delete); action = m_actions->addAction(QString::fromLatin1("annotationdialog-copy-previous"), this, SLOT(slotCopyPrevious())); action->setText(i18n("Copy tags from previous image")); m_actions->setDefaultShortcut(action, Qt::ALT + Qt::Key_Insert); action = m_actions->addAction(QString::fromLatin1("annotationdialog-rotate-left"), m_preview, SLOT(rotateLeft())); action->setText(i18n("Rotate counterclockwise")); action = m_actions->addAction(QString::fromLatin1("annotationdialog-rotate-right"), m_preview, SLOT(rotateRight())); action->setText(i18n("Rotate clockwise")); action = m_actions->addAction(QString::fromLatin1("annotationdialog-toggle-viewer"), this, SLOT(togglePreview())); action->setText(i18n("Toggle fullscreen preview")); m_actions->setDefaultShortcut(action, Qt::CTRL + Qt::Key_Space); foreach (QAction *action, m_actions->actions()) { action->setShortcutContext(Qt::WindowShortcut); addAction(action); } // the annotation dialog is created when it's first used; // therefore, its actions are registered well after the MainWindow sets up its actionCollection, // and it has to read the shortcuts here, after they are set up: m_actions->readSettings(); } KActionCollection *AnnotationDialog::Dialog::actions() { return m_actions; } void AnnotationDialog::Dialog::setUpCategoryListBoxForMultiImageSelection(ListSelect *listSel, const DB::ImageInfoList &images) { StringSet on, partialOn; std::tie(on, partialOn) = selectionForMultiSelect(listSel, images); listSel->setSelection(on, partialOn); } std::tuple AnnotationDialog::Dialog::selectionForMultiSelect(ListSelect *listSel, const DB::ImageInfoList &images) { const QString category = listSel->category(); const StringSet allItems = DB::ImageDB::instance()->categoryCollection()->categoryForName(category)->itemsInclCategories().toSet(); StringSet itemsNotSelectedOnAllImages; StringSet itemsOnSomeImages; for (DB::ImageInfoList::ConstIterator imageIt = images.begin(); imageIt != images.end(); ++imageIt) { const StringSet itemsOnThisImage = (*imageIt)->itemsOfCategory(category); itemsNotSelectedOnAllImages += (allItems - itemsOnThisImage); itemsOnSomeImages += itemsOnThisImage; } const StringSet itemsOnAllImages = allItems - itemsNotSelectedOnAllImages; const StringSet itemsPartiallyOn = itemsOnSomeImages - itemsOnAllImages; return std::make_tuple(itemsOnAllImages, itemsPartiallyOn); } void AnnotationDialog::Dialog::slotRatingChanged(unsigned int) { m_ratingChanged = true; } void AnnotationDialog::Dialog::continueLater() { saveAndClose(); } void AnnotationDialog::Dialog::saveAndClose() { tidyAreas(); m_fullScreenPreview->stopPlayback(); if (m_origList.isEmpty()) { // all images are deleted. QDialog::accept(); return; } // I need to check for the changes first, as the case for m_setup == InputSingleImageConfigMode, saves to the m_origList, // and we can thus not check for changes anymore const bool anyChanges = hasChanges(); if (m_setup == InputSingleImageConfigMode) { writeToInfo(); for (int i = 0; i < m_editList.count(); ++i) { *(m_origList[i]) = m_editList[i]; } } else if (m_setup == InputMultiImageConfigMode) { Q_FOREACH (ListSelect *ls, m_optionList) { ls->slotReturn(); } for (DB::ImageInfoListConstIterator it = m_origList.constBegin(); it != m_origList.constEnd(); ++it) { DB::ImageInfoPtr info = *it; if (!m_startDate->date().isNull()) info->setDate(DB::ImageDate(m_startDate->date(), m_endDate->date(), m_time->time())); Q_FOREACH (ListSelect *ls, m_optionList) { info->addCategoryInfo(ls->category(), ls->itemsOn()); info->removeCategoryInfo(ls->category(), ls->itemsOff()); } if (!m_imageLabel->text().isEmpty()) { info->setLabel(m_imageLabel->text()); } if (!m_description->toPlainText().isEmpty() && m_description->toPlainText().compare(m_conflictText)) { info->setDescription(m_description->toPlainText()); } if (m_ratingChanged) { info->setRating(m_rating->rating()); } } m_ratingChanged = false; } m_accept = QDialog::Accepted; if (anyChanges) { MainWindow::DirtyIndicator::markDirty(); } QDialog::accept(); } AnnotationDialog::Dialog::~Dialog() { qDeleteAll(m_optionList); m_optionList.clear(); } void AnnotationDialog::Dialog::togglePreview() { if (m_setup == InputSingleImageConfigMode) { if (m_stack->currentWidget() == m_fullScreenPreview) { m_stack->setCurrentWidget(m_dockWindow); m_fullScreenPreview->stopPlayback(); } else { DB::ImageInfo currentInfo = m_editList[m_current]; m_stack->setCurrentWidget(m_fullScreenPreview); m_fullScreenPreview->load(DB::FileNameList() << currentInfo.fileName()); // compute altered tags by removing existing tags from full set: const QMap> existingAreas = currentInfo.taggedAreas(); QMap> alteredAreas = taggedAreas(); for (auto catIt = existingAreas.constBegin(); catIt != existingAreas.constEnd(); ++catIt) { const QString &categoryName = catIt.key(); const QMap &tags = catIt.value(); for (auto tagIt = tags.cbegin(); tagIt != tags.constEnd(); ++tagIt) { const QString &tagName = tagIt.key(); const QRect &area = tagIt.value(); // remove unchanged areas if (area == alteredAreas[categoryName][tagName]) { alteredAreas[categoryName].remove(tagName); if (alteredAreas[categoryName].empty()) alteredAreas.remove(categoryName); } } } m_fullScreenPreview->addAdditionalTaggedAreas(alteredAreas); } } } void AnnotationDialog::Dialog::tidyAreas() { // Remove all areas marked on the preview image foreach (ResizableFrame *area, areas()) { area->markTidied(); area->deleteLater(); } } void AnnotationDialog::Dialog::slotNewArea(ResizableFrame *area) { area->setDialog(this); } void AnnotationDialog::Dialog::positionableTagSelected(QString category, QString tag) { // Be sure not to propose an already-associated tag QPair tagData = qMakePair(category, tag); foreach (ResizableFrame *area, areas()) { if (area->tagData() == tagData) { return; } } // Set the selected tag as the last selected positionable tag m_lastSelectedPositionableTag = tagData; // Add the tag to the positionable tag candidate list addTagToCandidateList(category, tag); } void AnnotationDialog::Dialog::positionableTagDeselected(QString category, QString tag) { // Remove the tag from the candidate list removeTagFromCandidateList(category, tag); // Search for areas linked against the tag on this image if (m_setup == InputSingleImageConfigMode) { QPair deselectedTag = QPair(category, tag); foreach (ResizableFrame *area, areas()) { if (area->tagData() == deselectedTag) { area->removeTagData(); m_areasChanged = true; // Only one area can be associated with the tag, so we can return here return; } } } // Removal of tagged areas in InputMultiImageConfigMode is done in DB::ImageInfo::removeCategoryInfo } void AnnotationDialog::Dialog::addTagToCandidateList(QString category, QString tag) { m_positionableTagCandidates << QPair(category, tag); } void AnnotationDialog::Dialog::removeTagFromCandidateList(QString category, QString tag) { // Is the deselected tag the last selected positionable tag? if (m_lastSelectedPositionableTag.first == category && m_lastSelectedPositionableTag.second == tag) { m_lastSelectedPositionableTag = QPair(); } // Remove the tag from the candidate list m_positionableTagCandidates.removeAll(QPair(category, tag)); // When a positionable tag is entered via the AreaTagSelectDialog, it's added to this // list twice, so we use removeAll here to be sure to also wipe duplicate entries. } QPair AnnotationDialog::Dialog::lastSelectedPositionableTag() const { return m_lastSelectedPositionableTag; } QList> AnnotationDialog::Dialog::positionableTagCandidates() const { return m_positionableTagCandidates; } void AnnotationDialog::Dialog::slotShowAreas(bool showAreas) { foreach (ResizableFrame *area, areas()) { area->setVisible(showAreas); } } void AnnotationDialog::Dialog::positionableTagRenamed(QString category, QString oldTag, QString newTag) { // Is the renamed tag the last selected positionable tag? if (m_lastSelectedPositionableTag.first == category && m_lastSelectedPositionableTag.second == oldTag) { m_lastSelectedPositionableTag.second = newTag; } // Check the candidate list for the tag QPair oldTagData = QPair(category, oldTag); if (m_positionableTagCandidates.contains(oldTagData)) { // The tag is in the list, so update it m_positionableTagCandidates.removeAt(m_positionableTagCandidates.indexOf(oldTagData)); m_positionableTagCandidates << QPair(category, newTag); } // Check if an area on the current image contains the changed or proposed tag foreach (ResizableFrame *area, areas()) { if (area->tagData() == oldTagData) { area->setTagData(category, newTag); } } } void AnnotationDialog::Dialog::descriptionPageUpDownPressed(QKeyEvent *event) { if (event->key() == Qt::Key_PageUp) { m_actions->action(QString::fromLatin1("annotationdialog-prev-image"))->trigger(); } else if (event->key() == Qt::Key_PageDown) { m_actions->action(QString::fromLatin1("annotationdialog-next-image"))->trigger(); } } void AnnotationDialog::Dialog::checkProposedTagData( QPair tagData, ResizableFrame *areaToExclude) const { foreach (ResizableFrame *area, areas()) { if (area != areaToExclude && area->proposedTagData() == tagData && area->tagData().first.isEmpty()) { area->removeProposedTagData(); } } } void AnnotationDialog::Dialog::areaChanged() { m_areasChanged = true; } /** * @brief positionableTagValid checks whether a given tag can still be associated to an area. * This checks for empty and duplicate tags. * @return */ bool AnnotationDialog::Dialog::positionableTagAvailable(const QString &category, const QString &tag) const { if (category.isEmpty() || tag.isEmpty()) return false; // does any area already have that tag? foreach (const ResizableFrame *area, areas()) { const auto tagData = area->tagData(); if (tagData.first == category && tagData.second == tag) return false; } return true; } /** * @brief Generates a set of positionable tags currently used on the image * @param category * @return */ QSet AnnotationDialog::Dialog::positionedTags(const QString &category) const { QSet tags; foreach (const ResizableFrame *area, areas()) { const auto tagData = area->tagData(); if (tagData.first == category) tags += tagData.second; } return tags; } AnnotationDialog::ListSelect *AnnotationDialog::Dialog::listSelectForCategory(const QString &category) { return m_listSelectList.value(category, nullptr); } #ifdef HAVE_KGEOMAP void AnnotationDialog::Dialog::updateMapForCurrentImage() { if (m_setup != InputSingleImageConfigMode) { return; } if (m_editList[m_current].coordinates().hasCoordinates()) { m_annotationMap->setCenter(m_editList[m_current]); m_annotationMap->displayStatus(Map::MapView::MapStatus::ImageHasCoordinates); } else { m_annotationMap->displayStatus(Map::MapView::MapStatus::ImageHasNoCoordinates); } } void AnnotationDialog::Dialog::annotationMapVisibilityChanged(bool visible) { // This populates the map if it's added when the dialog is already open if (visible) { // when the map dockwidget is already visible on show(), the call to // annotationMapVisibilityChanged is executed in the GUI thread. // This ensures that populateMap() doesn't block the GUI in this case: QTimer::singleShot(0, this, SLOT(populateMap())); } else { m_cancelMapLoading = true; } } void AnnotationDialog::Dialog::populateMap() { // populateMap is called every time the map widget gets visible if (m_mapIsPopulated) { return; } m_annotationMap->displayStatus(Map::MapView::MapStatus::Loading); m_cancelMapLoading = false; m_mapLoadingProgress->setMaximum(m_editList.count()); m_mapLoadingProgress->show(); m_cancelMapLoadingButton->show(); int processedImages = 0; int imagesWithCoordinates = 0; foreach (DB::ImageInfo info, m_editList) { processedImages++; m_mapLoadingProgress->setValue(processedImages); // keep things responsive by processing events manually: QApplication::processEvents(); if (info.coordinates().hasCoordinates()) { m_annotationMap->addImage(info); imagesWithCoordinates++; } // m_cancelMapLoading is set to true by clicking the "Cancel" button if (m_cancelMapLoading) { m_annotationMap->clear(); break; } } // at this point either we canceled loading or the map is populated: m_mapIsPopulated = !m_cancelMapLoading; mapLoadingFinished(imagesWithCoordinates > 0, imagesWithCoordinates == processedImages); } void AnnotationDialog::Dialog::setCancelMapLoading() { m_cancelMapLoading = true; } void AnnotationDialog::Dialog::mapLoadingFinished(bool mapHasImages, bool allImagesHaveCoordinates) { m_mapLoadingProgress->hide(); m_cancelMapLoadingButton->hide(); if (m_setup == InputSingleImageConfigMode) { m_annotationMap->displayStatus(Map::MapView::MapStatus::ImageHasNoCoordinates); } else { if (m_setup == SearchMode) { m_annotationMap->displayStatus(Map::MapView::MapStatus::SearchCoordinates); } else { if (mapHasImages) { if (!allImagesHaveCoordinates) { m_annotationMap->displayStatus(Map::MapView::MapStatus::SomeImagesHaveNoCoordinates); } else { m_annotationMap->displayStatus(Map::MapView::MapStatus::ImageHasCoordinates); } } else { m_annotationMap->displayStatus(Map::MapView::MapStatus::NoImagesHaveNoCoordinates); } } } if (m_setup != SearchMode) { m_annotationMap->zoomToMarkers(); updateMapForCurrentImage(); } } #endif // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/AnnotationDialog/ListSelect.cpp b/AnnotationDialog/ListSelect.cpp index 7c9278e2..219f3e06 100644 --- a/AnnotationDialog/ListSelect.cpp +++ b/AnnotationDialog/ListSelect.cpp @@ -1,861 +1,869 @@ /* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ListSelect.h" #include "CompletableLineEdit.h" #include "Dialog.h" #include "ListViewItemHider.h" #include "ShowSelectionOnlyManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace AnnotationDialog; using CategoryListView::CheckDropItem; +namespace +{ +inline QPixmap smallIcon(const QString &iconName) +{ + return QIcon::fromTheme(iconName).pixmap(KIconLoader::StdSizes::SizeSmall); +} +} + AnnotationDialog::ListSelect::ListSelect(const DB::CategoryPtr &category, QWidget *parent) : QWidget(parent) , m_category(category) , m_baseTitle() { QVBoxLayout *layout = new QVBoxLayout(this); m_lineEdit = new CompletableLineEdit(this); m_lineEdit->setProperty("FocusCandidate", true); m_lineEdit->setProperty("WantsFocus", true); layout->addWidget(m_lineEdit); // PENDING(blackie) rename instance variable to something better than _listView m_treeWidget = new CategoryListView::DragableTreeWidget(m_category, this); m_treeWidget->setHeaderLabel(QString::fromLatin1("items")); m_treeWidget->header()->hide(); connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemClicked, this, &ListSelect::itemSelected); m_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_treeWidget, &CategoryListView::DragableTreeWidget::customContextMenuRequested, this, &ListSelect::showContextMenu); connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemsChanged, this, &ListSelect::rePopulate); connect(m_treeWidget, &CategoryListView::DragableTreeWidget::itemClicked, this, &ListSelect::updateSelectionCount); layout->addWidget(m_treeWidget); // Merge CheckBox QHBoxLayout *lay2 = new QHBoxLayout; layout->addLayout(lay2); m_or = new QRadioButton(i18n("or"), this); m_and = new QRadioButton(i18n("and"), this); lay2->addWidget(m_or); lay2->addWidget(m_and); lay2->addStretch(1); // Sorting tool button QButtonGroup *grp = new QButtonGroup(this); grp->setExclusive(true); m_alphaTreeSort = new QToolButton; - m_alphaTreeSort->setIcon(SmallIcon(QString::fromLatin1("view-list-tree"))); + m_alphaTreeSort->setIcon(smallIcon(QString::fromLatin1("view-list-tree"))); m_alphaTreeSort->setCheckable(true); m_alphaTreeSort->setToolTip(i18n("Sort Alphabetically (Tree)")); grp->addButton(m_alphaTreeSort); m_alphaFlatSort = new QToolButton; - m_alphaFlatSort->setIcon(SmallIcon(QString::fromLatin1("draw-text"))); + m_alphaFlatSort->setIcon(smallIcon(QString::fromLatin1("draw-text"))); m_alphaFlatSort->setCheckable(true); m_alphaFlatSort->setToolTip(i18n("Sort Alphabetically (Flat)")); grp->addButton(m_alphaFlatSort); m_dateSort = new QToolButton; - m_dateSort->setIcon(SmallIcon(QString::fromLatin1("x-office-calendar"))); + m_dateSort->setIcon(smallIcon(QString::fromLatin1("x-office-calendar"))); m_dateSort->setCheckable(true); m_dateSort->setToolTip(i18n("Sort by date")); grp->addButton(m_dateSort); m_showSelectedOnly = new QToolButton; - m_showSelectedOnly->setIcon(SmallIcon(QString::fromLatin1("view-filter"))); + m_showSelectedOnly->setIcon(smallIcon(QString::fromLatin1("view-filter"))); m_showSelectedOnly->setCheckable(true); m_showSelectedOnly->setToolTip(i18n("Show only selected Ctrl+S")); m_showSelectedOnly->setChecked(ShowSelectionOnlyManager::instance().selectionIsLimited()); m_alphaTreeSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); m_alphaFlatSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); m_dateSort->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); connect(m_dateSort, &QToolButton::clicked, this, &ListSelect::slotSortDate); connect(m_alphaTreeSort, &QToolButton::clicked, this, &ListSelect::slotSortAlphaTree); connect(m_alphaFlatSort, &QToolButton::clicked, this, &ListSelect::slotSortAlphaFlat); connect(m_showSelectedOnly, SIGNAL(clicked()), &ShowSelectionOnlyManager::instance(), SLOT(toggle())); lay2->addWidget(m_alphaTreeSort); lay2->addWidget(m_alphaFlatSort); lay2->addWidget(m_dateSort); lay2->addWidget(m_showSelectedOnly); connectLineEdit(m_lineEdit); populate(); connect(Settings::SettingsData::instance(), SIGNAL(viewSortTypeChanged(Settings::ViewSortType)), this, SLOT(setViewSortType(Settings::ViewSortType))); connect(Settings::SettingsData::instance(), SIGNAL(matchTypeChanged(AnnotationDialog::MatchType)), this, SLOT(updateListview())); connect(&ShowSelectionOnlyManager::instance(), SIGNAL(limitToSelected()), this, SLOT(limitToSelection())); connect(&ShowSelectionOnlyManager::instance(), SIGNAL(broaden()), this, SLOT(showAllChildren())); } void AnnotationDialog::ListSelect::slotReturn() { if (isInputMode()) { QString enteredText = m_lineEdit->text().trimmed(); if (enteredText.isEmpty()) { return; } if (searchForUntaggedImagesTagNeeded()) { if (enteredText == Settings::SettingsData::instance()->untaggedTag()) { KMessageBox::information( this, i18n("The tag you entered is the tag that is set automatically for newly " "found, untagged images (cf. Settings|Configure KPhotoAlbum..." "|Categories|Untagged Images). It will not show up here as " "long as it is selected for this purpose.")); m_lineEdit->setText(QString()); return; } } m_category->addItem(enteredText); rePopulate(); QList items = m_treeWidget->findItems(enteredText, Qt::MatchExactly | Qt::MatchRecursive, 0); if (!items.isEmpty()) { items.at(0)->setCheckState(0, Qt::Checked); if (m_positionable) { emit positionableTagSelected(m_category->name(), items.at(0)->text(0)); } } else { Q_ASSERT(false); } m_lineEdit->clear(); } updateSelectionCount(); } void ListSelect::slotExternalReturn(const QString &text) { m_lineEdit->setText(text); slotReturn(); } QString AnnotationDialog::ListSelect::category() const { return m_category->name(); } void AnnotationDialog::ListSelect::setSelection(const StringSet &on, const StringSet &partiallyOn) { for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { if (partiallyOn.contains((*itemIt)->text(0))) (*itemIt)->setCheckState(0, Qt::PartiallyChecked); else (*itemIt)->setCheckState(0, on.contains((*itemIt)->text(0)) ? Qt::Checked : Qt::Unchecked); } m_lineEdit->clear(); updateSelectionCount(); } bool AnnotationDialog::ListSelect::isAND() const { return m_and->isChecked(); } void AnnotationDialog::ListSelect::setMode(UsageMode mode) { m_mode = mode; m_lineEdit->setMode(mode); if (mode == SearchMode) { // "0" below is sorting key which ensures that None is always at top. CheckDropItem *item = new CheckDropItem(m_treeWidget, DB::ImageDB::NONE(), QString::fromLatin1("0")); configureItem(item); m_and->show(); m_or->show(); m_or->setChecked(true); m_showSelectedOnly->hide(); } else { m_and->hide(); m_or->hide(); m_showSelectedOnly->show(); } for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) configureItem(dynamic_cast(*itemIt)); // ensure that the selection count indicator matches the current mode: updateSelectionCount(); } void AnnotationDialog::ListSelect::setViewSortType(Settings::ViewSortType tp) { showAllChildren(); // set sortType and redisplay with new sortType QString text = m_lineEdit->text(); rePopulate(); m_lineEdit->setText(text); setMode(m_mode); // generate the ***NONE*** entry if in search mode m_alphaTreeSort->setChecked(tp == Settings::SortAlphaTree); m_alphaFlatSort->setChecked(tp == Settings::SortAlphaFlat); m_dateSort->setChecked(tp == Settings::SortLastUse); } QString AnnotationDialog::ListSelect::text() const { return m_lineEdit->text(); } void AnnotationDialog::ListSelect::setText(const QString &text) { m_lineEdit->setText(text); m_treeWidget->clearSelection(); } void AnnotationDialog::ListSelect::itemSelected(QTreeWidgetItem *item) { if (!item) { // click outside any item return; } if (m_mode == SearchMode) { QString txt = item->text(0); QString res; QRegExp regEnd(QString::fromLatin1("\\s*[&|!]\\s*$")); QRegExp regStart(QString::fromLatin1("^\\s*[&|!]\\s*")); if (item->checkState(0) == Qt::Checked) { int matchPos = m_lineEdit->text().indexOf(txt); if (matchPos != -1) { return; } int index = m_lineEdit->cursorPosition(); QString start = m_lineEdit->text().left(index); QString end = m_lineEdit->text().mid(index); res = start; if (!start.isEmpty() && !start.contains(regEnd)) { res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | "); } res += txt; if (!end.isEmpty() && !end.contains(regStart)) { res += isAND() ? QString::fromLatin1(" & ") : QString::fromLatin1(" | "); } res += end; } else { int index = m_lineEdit->text().indexOf(txt); if (index == -1) return; QString start = m_lineEdit->text().left(index); QString end = m_lineEdit->text().mid(index + txt.length()); if (start.contains(regEnd)) start.replace(regEnd, QString::fromLatin1("")); else end.replace(regStart, QString::fromLatin1("")); res = start + end; } m_lineEdit->setText(res); } else { if (m_positionable) { if (item->checkState(0) == Qt::Checked) { emit positionableTagSelected(m_category->name(), item->text(0)); } else { emit positionableTagDeselected(m_category->name(), item->text(0)); } } m_lineEdit->clear(); showAllChildren(); ensureAllInstancesAreStateChanged(item); } } void AnnotationDialog::ListSelect::showContextMenu(const QPoint &pos) { QMenu *menu = new QMenu(this); QTreeWidgetItem *item = m_treeWidget->itemAt(pos); // click on any item QString title = i18n("No Item Selected"); if (item) title = item->text(0); QLabel *label = new QLabel(i18n("%1", title), menu); label->setAlignment(Qt::AlignCenter); QWidgetAction *action = new QWidgetAction(menu); action->setDefaultWidget(label); menu->addAction(action); - QAction *deleteAction = menu->addAction(SmallIcon(QString::fromLatin1("edit-delete")), i18n("Delete")); + QAction *deleteAction = menu->addAction(smallIcon(QString::fromLatin1("edit-delete")), i18n("Delete")); QAction *renameAction = menu->addAction(i18n("Rename...")); QLabel *categoryTitle = new QLabel(i18n("Tag Groups"), menu); categoryTitle->setAlignment(Qt::AlignCenter); action = new QWidgetAction(menu); action->setDefaultWidget(categoryTitle); menu->addAction(action); // -------------------------------------------------- Add/Remove member group DB::MemberMap &memberMap = DB::ImageDB::instance()->memberMap(); QMenu *members = new QMenu(i18n("Tag groups")); menu->addMenu(members); QAction *newCategoryAction = nullptr; if (item) { QStringList grps = memberMap.groups(m_category->name()); for (QStringList::ConstIterator it = grps.constBegin(); it != grps.constEnd(); ++it) { if (!memberMap.canAddMemberToGroup(m_category->name(), *it, item->text(0))) continue; QAction *action = members->addAction(*it); action->setCheckable(true); action->setChecked((bool)memberMap.members(m_category->name(), *it, false).contains(item->text(0))); action->setData(*it); } if (!grps.isEmpty()) members->addSeparator(); newCategoryAction = members->addAction(i18n("Add this tag to a new tag group...")); } QAction *newSubcategoryAction = menu->addAction(i18n("Make this tag a tag group and add a tag...")); // -------------------------------------------------- Take item out of category QTreeWidgetItem *parent = item ? item->parent() : nullptr; QAction *takeAction = nullptr; if (parent) takeAction = menu->addAction(i18n("Remove from tag group %1", parent->text(0))); // -------------------------------------------------- sort QLabel *sortTitle = new QLabel(i18n("Sorting")); sortTitle->setAlignment(Qt::AlignCenter); action = new QWidgetAction(menu); action->setDefaultWidget(sortTitle); menu->addAction(action); QAction *usageAction = menu->addAction(i18n("Usage")); QAction *alphaFlatAction = menu->addAction(i18n("Alphabetical (Flat)")); QAction *alphaTreeAction = menu->addAction(i18n("Alphabetical (Tree)")); usageAction->setCheckable(true); usageAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortLastUse); alphaFlatAction->setCheckable(true); alphaFlatAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat); alphaTreeAction->setCheckable(true); alphaTreeAction->setChecked(Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree); if (!item) { deleteAction->setEnabled(false); renameAction->setEnabled(false); members->setEnabled(false); newSubcategoryAction->setEnabled(false); } // -------------------------------------------------- exec QAction *which = menu->exec(m_treeWidget->mapToGlobal(pos)); if (which == nullptr) return; else if (which == deleteAction) { Q_ASSERT(item); int code = KMessageBox::warningContinueCancel(this, i18n("

Do you really want to delete \"%1\"?
" "Deleting the item will remove any information " "about it from any image containing the item.

", title), i18n("Really Delete %1?", item->text(0)), KGuiItem(i18n("&Delete"), QString::fromLatin1("editdelete"))); if (code == KMessageBox::Continue) { if (item->checkState(0) == Qt::Checked && m_positionable) { // An area could be linked against this. We can use positionableTagDeselected // here, as the procedure is the same as if the tag had been deselected. emit positionableTagDeselected(m_category->name(), item->text(0)); } m_category->removeItem(item->text(0)); rePopulate(); } } else if (which == renameAction) { Q_ASSERT(item); bool ok; QString newStr = QInputDialog::getText(this, i18n("Rename Item"), i18n("Enter new name:"), QLineEdit::Normal, item->text(0), &ok); if (ok && !newStr.isEmpty() && newStr != item->text(0)) { int code = KMessageBox::questionYesNo(this, i18n("

Do you really want to rename \"%1\" to \"%2\"?
" "Doing so will rename \"%3\" " "on any image containing it.

", item->text(0), newStr, item->text(0)), i18n("Really Rename %1?", item->text(0))); if (code == KMessageBox::Yes) { QString oldStr = item->text(0); m_category->renameItem(oldStr, newStr); bool checked = item->checkState(0) == Qt::Checked; rePopulate(); // rePopuldate doesn't ask the backend if the item should be checked, so we need to do that. checkItem(newStr, checked); // rename the category image too QString oldFile = m_category->fileForCategoryImage(category(), oldStr); QString newFile = m_category->fileForCategoryImage(category(), newStr); KIO::move(QUrl::fromLocalFile(oldFile), QUrl::fromLocalFile(newFile)); if (m_positionable) { // Also take care of areas that could be linked against this emit positionableTagRenamed(m_category->name(), oldStr, newStr); } } } } else if (which == usageAction) { Settings::SettingsData::instance()->setViewSortType(Settings::SortLastUse); } else if (which == alphaTreeAction) { Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaTree); } else if (which == alphaFlatAction) { Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaFlat); } else if (which == newCategoryAction) { Q_ASSERT(item); QString superCategory = QInputDialog::getText(this, i18n("New tag group"), i18n("Name for the new tag group the tag will be added to:")); if (superCategory.isEmpty()) return; memberMap.addGroup(m_category->name(), superCategory); memberMap.addMemberToGroup(m_category->name(), superCategory, item->text(0)); //DB::ImageDB::instance()->setMemberMap( memberMap ); rePopulate(); } else if (which == newSubcategoryAction) { Q_ASSERT(item); QString subCategory = QInputDialog::getText(this, i18n("Add a tag"), i18n("Name for the tag to be added to this tag group:")); if (subCategory.isEmpty()) return; m_category->addItem(subCategory); memberMap.addGroup(m_category->name(), item->text(0)); memberMap.addMemberToGroup(m_category->name(), item->text(0), subCategory); //DB::ImageDB::instance()->setMemberMap( memberMap ); if (isInputMode()) m_category->addItem(subCategory); rePopulate(); if (isInputMode()) checkItem(subCategory, true); } else if (which == takeAction) { Q_ASSERT(item); memberMap.removeMemberFromGroup(m_category->name(), parent->text(0), item->text(0)); rePopulate(); } else { Q_ASSERT(item); QString checkedItem = which->data().value(); if (which->isChecked()) // choosing the item doesn't check it, so this is the value before. memberMap.addMemberToGroup(m_category->name(), checkedItem, item->text(0)); else memberMap.removeMemberFromGroup(m_category->name(), checkedItem, item->text(0)); rePopulate(); } delete menu; } void AnnotationDialog::ListSelect::addItems(DB::CategoryItem *item, QTreeWidgetItem *parent) { for (QList::ConstIterator subcategoryIt = item->mp_subcategories.constBegin(); subcategoryIt != item->mp_subcategories.constEnd(); ++subcategoryIt) { CheckDropItem *newItem = nullptr; if (parent == nullptr) newItem = new CheckDropItem(m_treeWidget, (*subcategoryIt)->mp_name, QString()); else newItem = new CheckDropItem(m_treeWidget, parent, (*subcategoryIt)->mp_name, QString()); newItem->setExpanded(true); configureItem(newItem); addItems(*subcategoryIt, newItem); } } void AnnotationDialog::ListSelect::populate() { m_treeWidget->clear(); if (Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree) populateAlphaTree(); else if (Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaFlat) populateAlphaFlat(); else populateMRU(); hideUntaggedImagesTag(); } bool AnnotationDialog::ListSelect::searchForUntaggedImagesTagNeeded() { if (!Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured() || Settings::SettingsData::instance()->untaggedImagesTagVisible()) { return false; } if (Settings::SettingsData::instance()->untaggedCategory() != category()) { return false; } return true; } void AnnotationDialog::ListSelect::hideUntaggedImagesTag() { if (!searchForUntaggedImagesTagNeeded()) { return; } QTreeWidgetItem *untaggedImagesTag = getUntaggedImagesTag(); if (untaggedImagesTag) { untaggedImagesTag->setHidden(true); } } void AnnotationDialog::ListSelect::slotSortDate() { Settings::SettingsData::instance()->setViewSortType(Settings::SortLastUse); } void AnnotationDialog::ListSelect::slotSortAlphaTree() { Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaTree); } void AnnotationDialog::ListSelect::slotSortAlphaFlat() { Settings::SettingsData::instance()->setViewSortType(Settings::SortAlphaFlat); } void AnnotationDialog::ListSelect::rePopulate() { const StringSet on = itemsOn(); const StringSet noChange = itemsUnchanged(); populate(); setSelection(on, noChange); if (ShowSelectionOnlyManager::instance().selectionIsLimited()) limitToSelection(); } void AnnotationDialog::ListSelect::showOnlyItemsMatching(const QString &text) { ListViewTextMatchHider dummy(text, Settings::SettingsData::instance()->matchType(), m_treeWidget); ShowSelectionOnlyManager::instance().unlimitFromSelection(); } void AnnotationDialog::ListSelect::populateAlphaTree() { DB::CategoryItemPtr item = m_category->itemsCategories(); m_treeWidget->setRootIsDecorated(true); addItems(item.data(), 0); m_treeWidget->sortByColumn(0, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::populateAlphaFlat() { QStringList items = m_category->itemsInclCategories(); items.sort(); m_treeWidget->setRootIsDecorated(false); for (QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) { CheckDropItem *item = new CheckDropItem(m_treeWidget, *itemIt, *itemIt); configureItem(item); } m_treeWidget->sortByColumn(1, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::populateMRU() { QStringList items = m_category->itemsInclCategories(); m_treeWidget->setRootIsDecorated(false); int index = 100000; // This counter will be converted to a string, and compared, and we don't want "1111" to be less than "2" for (QStringList::ConstIterator itemIt = items.constBegin(); itemIt != items.constEnd(); ++itemIt) { ++index; CheckDropItem *item = new CheckDropItem(m_treeWidget, *itemIt, QString::number(index)); configureItem(item); } m_treeWidget->sortByColumn(1, Qt::AscendingOrder); m_treeWidget->setSortingEnabled(true); } void AnnotationDialog::ListSelect::toggleSortType() { Settings::SettingsData *data = Settings::SettingsData::instance(); if (data->viewSortType() == Settings::SortLastUse) data->setViewSortType(Settings::SortAlphaTree); else if (data->viewSortType() == Settings::SortAlphaTree) data->setViewSortType(Settings::SortAlphaFlat); else data->setViewSortType(Settings::SortLastUse); } void AnnotationDialog::ListSelect::updateListview() { // update item list (e.g. when MatchType changes): showOnlyItemsMatching(text()); } void AnnotationDialog::ListSelect::limitToSelection() { if (!isInputMode()) return; m_showSelectedOnly->setChecked(true); ListViewCheckedHider dummy(m_treeWidget); hideUntaggedImagesTag(); } void AnnotationDialog::ListSelect::showAllChildren() { m_showSelectedOnly->setChecked(false); showOnlyItemsMatching(QString()); hideUntaggedImagesTag(); } QTreeWidgetItem *AnnotationDialog::ListSelect::getUntaggedImagesTag() { QList matchingTags = m_treeWidget->findItems( Settings::SettingsData::instance()->untaggedTag(), Qt::MatchExactly | Qt::MatchRecursive, 0); // Be sure not to crash here in case the config points to a non-existent tag if (matchingTags.at(0) == nullptr) { return 0; } else { return matchingTags.at(0); } } void AnnotationDialog::ListSelect::updateSelectionCount() { if (m_baseTitle.isEmpty() /* --> first time */ || !parentWidget()->windowTitle().startsWith(m_baseTitle) /* --> title has changed */) { // save the original parentWidget title m_baseTitle = parentWidget()->windowTitle(); } int itemsOnCount = itemsOn().size(); // Don't count the untagged images tag: if (searchForUntaggedImagesTagNeeded()) { QTreeWidgetItem *untaggedImagesTag = getUntaggedImagesTag(); if (untaggedImagesTag) { if (untaggedImagesTag->checkState(0) != Qt::Unchecked) { itemsOnCount--; } } } switch (m_mode) { case InputMultiImageConfigMode: if (itemsUnchanged().size() > 0) { // if min != max // tri-state selection -> show min-max (selected items vs. partially selected items): parentWidget()->setWindowTitle(i18nc( "Category name, then min-max of selected tags across several images. E.g. 'People (1-2)'", "%1 (%2-%3)", m_baseTitle, itemsOnCount, itemsOnCount + itemsUnchanged().size())); break; } // else fall through and only show one number: /* FALLTHROUGH */ case InputSingleImageConfigMode: if (itemsOnCount > 0) { // if any tags have been selected // "normal" on/off states -> show selected items parentWidget()->setWindowTitle( i18nc("Category name, then number of selected tags. E.g. 'People (1)'", "%1 (%2)", m_baseTitle, itemsOnCount)); break; } // else fall through and only show category /* FALLTHROUGH */ case SearchMode: // no indicator while searching parentWidget()->setWindowTitle(m_baseTitle); break; } } void AnnotationDialog::ListSelect::configureItem(CategoryListView::CheckDropItem *item) { bool isDNDAllowed = Settings::SettingsData::instance()->viewSortType() == Settings::SortAlphaTree; item->setDNDEnabled(isDNDAllowed && !m_category->isSpecialCategory()); } bool AnnotationDialog::ListSelect::isInputMode() const { return m_mode != SearchMode; } StringSet AnnotationDialog::ListSelect::itemsOn() const { return itemsOfState(Qt::Checked); } StringSet AnnotationDialog::ListSelect::itemsOff() const { return itemsOfState(Qt::Unchecked); } StringSet AnnotationDialog::ListSelect::itemsOfState(Qt::CheckState state) const { StringSet res; for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { if ((*itemIt)->checkState(0) == state) res.insert((*itemIt)->text(0)); } return res; } StringSet AnnotationDialog::ListSelect::itemsUnchanged() const { return itemsOfState(Qt::PartiallyChecked); } void AnnotationDialog::ListSelect::checkItem(const QString itemText, bool b) { QList items = m_treeWidget->findItems(itemText, Qt::MatchExactly | Qt::MatchRecursive); if (!items.isEmpty()) items.at(0)->setCheckState(0, b ? Qt::Checked : Qt::Unchecked); else Q_ASSERT(false); } /** * An item may be member of a number of categories. Mike may be a member of coworkers and friends. * Selecting the item in one subcategory, should select him in all. */ void AnnotationDialog::ListSelect::ensureAllInstancesAreStateChanged(QTreeWidgetItem *item) { const bool on = item->checkState(0) == Qt::Checked; for (QTreeWidgetItemIterator itemIt(m_treeWidget); *itemIt; ++itemIt) { if ((*itemIt) != item && (*itemIt)->text(0) == item->text(0)) (*itemIt)->setCheckState(0, on ? Qt::Checked : Qt::Unchecked); } } QWidget *AnnotationDialog::ListSelect::lineEdit() const { return m_lineEdit; } void AnnotationDialog::ListSelect::setPositionable(bool positionableState) { m_positionable = positionableState; } bool AnnotationDialog::ListSelect::positionable() const { return m_positionable; } bool AnnotationDialog::ListSelect::tagIsChecked(QString tag) const { QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); if (matchingTags.isEmpty()) { return false; } return (bool)matchingTags.first()->checkState(0); } /** * @brief ListSelect::connectLineEdit associates a CompletableLineEdit with this ListSelect * This method also allows to connect an external CompletableLineEdit to work with this ListSelect. * @param le */ void ListSelect::connectLineEdit(CompletableLineEdit *le) { le->setObjectName(m_category->name()); le->setListView(m_treeWidget); connect(le, &KLineEdit::returnPressed, this, &ListSelect::slotExternalReturn); } void AnnotationDialog::ListSelect::ensureTagIsSelected(QString category, QString tag) { if (category != m_lineEdit->objectName()) { // The selected tag's category does not belong to this ListSelect return; } // Be sure that tag is actually checked QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); // If we have the requested category, but not this tag, add it. // This should only happen if the recognition database is copied from another database // or has been changed outside of KPA. But this _can_ happen and simply adding a // missing tag does not hurt ;-) if (matchingTags.isEmpty()) { m_category->addItem(tag); rePopulate(); // Now, we find it matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); } matchingTags.first()->setCheckState(0, Qt::Checked); } void AnnotationDialog::ListSelect::deselectTag(QString tag) { QList matchingTags = m_treeWidget->findItems(tag, Qt::MatchExactly | Qt::MatchRecursive, 0); matchingTags.first()->setCheckState(0, Qt::Unchecked); } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/MainWindow/DirtyIndicator.cpp b/MainWindow/DirtyIndicator.cpp index b6bb850f..3ceedefa 100644 --- a/MainWindow/DirtyIndicator.cpp +++ b/MainWindow/DirtyIndicator.cpp @@ -1,95 +1,97 @@ -/* Copyright (C) 2003-2018 Jesper K. Pedersen +/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DirtyIndicator.h" +#include #include #include #include static MainWindow::DirtyIndicator *s_instance = nullptr; bool MainWindow::DirtyIndicator::s_autoSaveDirty = false; bool MainWindow::DirtyIndicator::s_saveDirty = false; bool MainWindow::DirtyIndicator::s_suppressMarkDirty = false; MainWindow::DirtyIndicator::DirtyIndicator(QWidget *parent) : QLabel(parent) { - m_dirtyPix = QPixmap(SmallIcon(QString::fromLatin1("media-floppy"))); + m_dirtyPix = QIcon::fromTheme(QString::fromLatin1("media-floppy")) + .pixmap(KIconLoader::StdSizes::SizeSmall); setFixedWidth(m_dirtyPix.width() + 10); s_instance = this; // Might have been marked dirty even before the indicator had been created, by the database searching during loading. if (s_saveDirty) markDirty(); } void MainWindow::DirtyIndicator::suppressMarkDirty(bool state) { MainWindow::DirtyIndicator::s_suppressMarkDirty = state; } void MainWindow::DirtyIndicator::markDirty() { if (MainWindow::DirtyIndicator::s_suppressMarkDirty) { return; } if (s_instance) { s_instance->markDirtySlot(); } else { s_saveDirty = true; s_autoSaveDirty = true; } } void MainWindow::DirtyIndicator::markDirtySlot() { if (MainWindow::DirtyIndicator::s_suppressMarkDirty) { return; } s_saveDirty = true; s_autoSaveDirty = true; setPixmap(m_dirtyPix); emit dirty(); } void MainWindow::DirtyIndicator::autoSaved() { s_autoSaveDirty = false; } void MainWindow::DirtyIndicator::saved() { s_autoSaveDirty = false; s_saveDirty = false; setPixmap(QPixmap()); } bool MainWindow::DirtyIndicator::isSaveDirty() const { return s_saveDirty; } bool MainWindow::DirtyIndicator::isAutoSaveDirty() const { return s_autoSaveDirty; } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/MainWindow/StatusBar.cpp b/MainWindow/StatusBar.cpp index c8aba40c..b9374f23 100644 --- a/MainWindow/StatusBar.cpp +++ b/MainWindow/StatusBar.cpp @@ -1,191 +1,192 @@ /* Copyright (C) 2003-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "StatusBar.h" #include "DirtyIndicator.h" #include "ImageCounter.h" #include #include #ifdef KPA_ENABLE_REMOTECONTROL #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::StatusBar::StatusBar() : QStatusBar() { QPalette pal = palette(); pal.setBrush(QPalette::Base, QApplication::palette().color(QPalette::Background)); pal.setBrush(QPalette::Background, QApplication::palette().color(QPalette::Background)); setPalette(pal); setupGUI(); m_pendingShowTimer = new QTimer(this); m_pendingShowTimer->setSingleShot(true); connect(m_pendingShowTimer, &QTimer::timeout, this, &StatusBar::showStatusBar); } void MainWindow::StatusBar::setupGUI() { setContentsMargins(7, 2, 7, 2); QWidget *indicators = new QWidget(this); QHBoxLayout *indicatorsHBoxLayout = new QHBoxLayout(indicators); indicatorsHBoxLayout->setMargin(0); indicatorsHBoxLayout->setSpacing(10); mp_dirtyIndicator = new DirtyIndicator(indicators); indicatorsHBoxLayout->addWidget(mp_dirtyIndicator); connect(DB::ImageDB::instance(), &DB::ImageDB::dirty, mp_dirtyIndicator, &DirtyIndicator::markDirtySlot); #ifdef KPA_ENABLE_REMOTECONTROL auto *remoteIndicator = new RemoteControl::ConnectionIndicator(indicators); indicatorsHBoxLayout->addWidget(remoteIndicator); #endif auto *jobIndicator = new BackgroundTaskManager::StatusIndicator(indicators); indicatorsHBoxLayout->addWidget(jobIndicator); m_progressBar = new QProgressBar(this); m_progressBar->setMinimumWidth(400); addPermanentWidget(m_progressBar, 0); m_cancel = new QToolButton(this); m_cancel->setIcon(QIcon::fromTheme(QString::fromLatin1("dialog-close"))); m_cancel->setShortcut(Qt::Key_Escape); addPermanentWidget(m_cancel, 0); connect(m_cancel, &QToolButton::clicked, this, &StatusBar::cancelRequest); connect(m_cancel, &QToolButton::clicked, this, &StatusBar::hideStatusBar); m_lockedIndicator = new QLabel(indicators); indicatorsHBoxLayout->addWidget(m_lockedIndicator); addPermanentWidget(indicators, 0); mp_partial = new ImageCounter(this); addPermanentWidget(mp_partial, 0); mp_selected = new ImageCounter(this); addPermanentWidget(mp_selected, 0); ImageCounter *total = new ImageCounter(this); addPermanentWidget(total, 0); total->setTotal(DB::ImageDB::instance()->totalCount()); connect(DB::ImageDB::instance(), &DB::ImageDB::totalChanged, total, &ImageCounter::setTotal); mp_pathIndicator = new BreadcrumbViewer; addWidget(mp_pathIndicator, 1); setProgressBarVisible(false); m_thumbnailSizeSlider = ThumbnailView::ThumbnailFacade::instance()->createResizeSlider(); addPermanentWidget(m_thumbnailSizeSlider, 0); // prevent stretching: m_thumbnailSizeSlider->setMaximumSize(m_thumbnailSizeSlider->size()); m_thumbnailSizeSlider->setMinimumSize(m_thumbnailSizeSlider->size()); m_thumbnailSizeSlider->hide(); m_thumbnailSettings = new QToolButton; m_thumbnailSettings->setIcon(QIcon::fromTheme(QString::fromUtf8("settings-configure"))); m_thumbnailSettings->setToolTip(i18n("Thumbnail settings...")); addPermanentWidget(m_thumbnailSettings, 0); m_thumbnailSettings->hide(); connect(m_thumbnailSettings, &QToolButton::clicked, this, &StatusBar::thumbnailSettingsRequested); } void MainWindow::StatusBar::setLocked(bool locked) { - static QPixmap *lockedPix = new QPixmap(SmallIcon(QString::fromLatin1("object-locked"))); - m_lockedIndicator->setFixedWidth(lockedPix->width()); + static QPixmap lockedPix = QIcon::fromTheme(QString::fromLatin1("object-locked")) + .pixmap(KIconLoader::StdSizes::SizeSmall); + m_lockedIndicator->setFixedWidth(lockedPix.width()); if (locked) - m_lockedIndicator->setPixmap(*lockedPix); + m_lockedIndicator->setPixmap(lockedPix); else m_lockedIndicator->setPixmap(QPixmap()); } void MainWindow::StatusBar::startProgress(const QString &text, int total) { m_progressBar->setFormat(text + QString::fromLatin1(": %p%")); m_progressBar->setMaximum(total); m_progressBar->setValue(0); m_pendingShowTimer->start(1000); // To avoid flicker we will only show the statusbar after 1 second. } void MainWindow::StatusBar::setProgress(int progress) { if (progress == m_progressBar->maximum()) hideStatusBar(); // If progress comes in to fast, then the UI will freeze from all time spent on updating the progressbar. static QTime time; if (time.isNull() || time.elapsed() > 200) { m_progressBar->setValue(progress); time.restart(); } } void MainWindow::StatusBar::setProgressBarVisible(bool show) { m_progressBar->setVisible(show); m_cancel->setVisible(show); } void MainWindow::StatusBar::showThumbnailSlider() { m_thumbnailSizeSlider->setVisible(true); m_thumbnailSettings->show(); } void MainWindow::StatusBar::hideThumbnailSlider() { m_thumbnailSizeSlider->setVisible(false); m_thumbnailSettings->hide(); } void MainWindow::StatusBar::enterEvent(QEvent *) { // make sure that breadcrumbs are not obscured by messages clearMessage(); } void MainWindow::StatusBar::hideStatusBar() { setProgressBarVisible(false); m_pendingShowTimer->stop(); } void MainWindow::StatusBar::showStatusBar() { setProgressBarVisible(true); } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Map/MapView.cpp b/Map/MapView.cpp index b0ac2e07..8a617867 100644 --- a/Map/MapView.cpp +++ b/Map/MapView.cpp @@ -1,239 +1,247 @@ -/* Copyright (C) 2014-2015 Tobias Leupold +/* Copyright (C) 2014-2019 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "MapView.h" #include "Logging.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include #include #include // Libkgeomap includes #include // Local includes #include "MapMarkerModelHelper.h" #include "SearchMarkerTiler.h" +namespace +{ +inline QPixmap smallIcon(const QString &iconName) +{ + return QIcon::fromTheme(iconName).pixmap(KIconLoader::StdSizes::SizeSmall); +} +} + Map::MapView::MapView(QWidget *parent, UsageType type) : QWidget(parent) { if (type == MapViewWindow) { setWindowFlags(Qt::Window); setAttribute(Qt::WA_DeleteOnClose); } QVBoxLayout *layout = new QVBoxLayout(this); m_statusLabel = new QLabel; m_statusLabel->setAlignment(Qt::AlignCenter); m_statusLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_statusLabel->hide(); layout->addWidget(m_statusLabel); m_mapWidget = new KGeoMap::MapWidget(this); layout->addWidget(m_mapWidget); QWidget *controlWidget = m_mapWidget->getControlWidget(); controlWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); layout->addWidget(controlWidget); m_mapWidget->setActive(true); QPushButton *saveButton = new QPushButton; - saveButton->setIcon(QPixmap(SmallIcon(QString::fromUtf8("media-floppy")))); + saveButton->setIcon(QPixmap(smallIcon(QString::fromUtf8("media-floppy")))); saveButton->setToolTip(i18n("Save the current map settings")); m_mapWidget->addWidgetToControlWidget(saveButton); connect(saveButton, &QPushButton::clicked, this, &MapView::saveSettings); m_setLastCenterButton = new QPushButton; - m_setLastCenterButton->setIcon(QPixmap(SmallIcon(QString::fromUtf8("go-first")))); + m_setLastCenterButton->setIcon(QPixmap(smallIcon(QString::fromUtf8("go-first")))); m_setLastCenterButton->setToolTip(i18n("Go to last map position")); m_mapWidget->addWidgetToControlWidget(m_setLastCenterButton); connect(m_setLastCenterButton, &QPushButton::clicked, this, &MapView::setLastCenter); // We first try set the default backend "marble" or the first one available ... const QString defaultBackend = QString::fromUtf8("marble"); auto backends = m_mapWidget->availableBackends(); if (backends.contains(defaultBackend)) { m_mapWidget->setBackend(defaultBackend); } else { qCDebug(MapLog) << "AnnotationMap: using backend " << backends[0]; m_mapWidget->setBackend(backends[0]); } // ... then we try to set the (probably) saved settings KConfigGroup configGroup = KSharedConfig::openConfig()->group(QString::fromUtf8("MapView")); m_mapWidget->readSettingsFromGroup(&configGroup); // Add the item model for the coordinates display m_modelHelper = new MapMarkerModelHelper(); m_itemMarkerTiler = new SearchMarkerTiler(m_modelHelper, this); m_mapWidget->setGroupedModel(m_itemMarkerTiler); connect(m_mapWidget, &KGeoMap::MapWidget::signalRegionSelectionChanged, this, &MapView::signalRegionSelectionChanged); } Map::MapView::~MapView() { delete m_modelHelper; delete m_itemMarkerTiler; } void Map::MapView::clear() { m_modelHelper->clearItems(); } void Map::MapView::addImage(const DB::ImageInfo &image) { m_modelHelper->addImage(image); } void Map::MapView::addImage(const DB::ImageInfoPtr image) { m_modelHelper->addImage(image); } void Map::MapView::zoomToMarkers() { if (m_modelHelper->model()->rowCount() > 0) { m_mapWidget->adjustBoundariesToGroupedMarkers(); } m_lastCenter = m_mapWidget->getCenter(); } void Map::MapView::setCenter(const DB::ImageInfo &image) { m_lastCenter = image.coordinates(); m_mapWidget->setCenter(m_lastCenter); } void Map::MapView::setCenter(const DB::ImageInfoPtr image) { m_lastCenter = image->coordinates(); m_mapWidget->setCenter(m_lastCenter); } void Map::MapView::saveSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup configGroup = config->group(QString::fromUtf8("MapView")); m_mapWidget->saveSettingsToGroup(&configGroup); config->sync(); KMessageBox::information(this, i18n("Settings saved"), i18n("Map view")); } void Map::MapView::setShowThumbnails(bool state) { m_mapWidget->setShowThumbnails(state); } void Map::MapView::displayStatus(MapStatus status) { switch (status) { case MapStatus::Loading: m_statusLabel->setText(i18n("Loading coordinates from the images ...")); m_statusLabel->show(); m_mapWidget->hide(); m_mapWidget->clearRegionSelection(); m_setLastCenterButton->setEnabled(false); break; case MapStatus::ImageHasCoordinates: m_statusLabel->hide(); m_mapWidget->setAvailableMouseModes(KGeoMap::MouseModePan); m_mapWidget->setVisibleMouseModes(0); m_mapWidget->setMouseMode(KGeoMap::MouseModePan); m_mapWidget->clearRegionSelection(); m_mapWidget->show(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(true); break; case MapStatus::ImageHasNoCoordinates: m_statusLabel->setText(i18n("This image does not contain geographic coordinates.")); m_statusLabel->show(); m_mapWidget->hide(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(false); break; case MapStatus::SomeImagesHaveNoCoordinates: m_statusLabel->setText(i18n("Some of the selected images do not contain geographic " "coordinates.")); m_statusLabel->show(); m_mapWidget->setAvailableMouseModes(KGeoMap::MouseModePan); m_mapWidget->setVisibleMouseModes(0); m_mapWidget->setMouseMode(KGeoMap::MouseModePan); m_mapWidget->clearRegionSelection(); m_mapWidget->show(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(true); break; case MapStatus::SearchCoordinates: m_statusLabel->setText(i18n("Search for geographic coordinates.")); m_statusLabel->show(); m_mapWidget->setAvailableMouseModes(KGeoMap::MouseModePan | KGeoMap::MouseModeRegionSelectionFromIcon | KGeoMap::MouseModeRegionSelection); m_mapWidget->setVisibleMouseModes(KGeoMap::MouseModePan | KGeoMap::MouseModeRegionSelectionFromIcon | KGeoMap::MouseModeRegionSelection); m_mapWidget->setMouseMode(KGeoMap::MouseModeRegionSelectionFromIcon); m_mapWidget->show(); m_mapWidget->setCenter(KGeoMap::GeoCoordinates()); m_setLastCenterButton->hide(); break; case MapStatus::NoImagesHaveNoCoordinates: m_statusLabel->setText(i18n("None of the selected images contain geographic " "coordinates.")); m_statusLabel->show(); m_mapWidget->hide(); m_setLastCenterButton->show(); m_setLastCenterButton->setEnabled(false); break; } emit displayStatusChanged(status); } void Map::MapView::setLastCenter() { m_mapWidget->setCenter(m_lastCenter); } KGeoMap::GeoCoordinates::Pair Map::MapView::getRegionSelection() const { return m_mapWidget->getRegionSelection(); } bool Map::MapView::regionSelected() const { return m_mapWidget->getRegionSelection().first.hasCoordinates() && m_mapWidget->getRegionSelection().second.hasCoordinates(); } // vi:expandtab:tabstop=4 shiftwidth=4: