diff --git a/core/app/filters/filtersidebarwidget.cpp b/core/app/filters/filtersidebarwidget.cpp index 04661bc9c8..218b7638e4 100644 --- a/core/app/filters/filtersidebarwidget.cpp +++ b/core/app/filters/filtersidebarwidget.cpp @@ -1,502 +1,502 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2000-12-05 * Description : filters view for the right sidebar * * Copyright (C) 2009-2010 by Johannes Wienke * Copyright (C) 2010-2011 by Andi Clemens * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2014 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "filtersidebarwidget.h" // Qt includes #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dlayoutbox.h" #include "dexpanderbox.h" #include "digikam_debug.h" #include "applicationsettings.h" #include "colorlabelfilter.h" #include "geolocationfilter.h" #include "picklabelfilter.h" #include "ratingfilter.h" #include "mimefilter.h" #include "tagfilterview.h" namespace Digikam { class Q_DECL_HIDDEN FilterSideBarWidget::Private { public: explicit Private() : space(nullptr), expanderVlay(nullptr), tagFilterView(nullptr), tagFilterSearchBar(nullptr), tagOptionsBtn(nullptr), tagOptionsMenu(nullptr), tagFilterModel(nullptr), tagOrCondAction(nullptr), tagAndCondAction(nullptr), tagMatchCond(ItemFilterSettings::OrCondition), colorLabelFilter(nullptr), geolocationFilter(nullptr), pickLabelFilter(nullptr), ratingFilter(nullptr), mimeFilter(nullptr), textFilter(nullptr), withoutTagCheckBox(nullptr), expbox(nullptr) { } static const QString configSearchTextFilterFieldsEntry; static const QString configLastShowUntaggedEntry; static const QString configMatchingConditionEntry; QWidget* space; QVBoxLayout* expanderVlay; TagFilterView* tagFilterView; SearchTextBar* tagFilterSearchBar; QToolButton* tagOptionsBtn; QMenu* tagOptionsMenu; TagModel* tagFilterModel; QAction* tagOrCondAction; QAction* tagAndCondAction; ItemFilterSettings::MatchingCondition tagMatchCond; TagFilterView* faceFilterView; SearchTextBar* faceFilterSearchBar; QToolButton* faceOptionsBtn; QMenu* faceOptionsMenu; TagModel* faceFilterModel; QAction* faceOrCondAction; QAction* faceAndCondAction; ItemFilterSettings::MatchingCondition faceMatchCond; ColorLabelFilter* colorLabelFilter; GeolocationFilter* geolocationFilter; PickLabelFilter* pickLabelFilter; RatingFilter* ratingFilter; MimeFilter* mimeFilter; TextFilter* textFilter; QCheckBox* withoutTagCheckBox; QCheckBox* withoutFaceCheckBox; DExpanderBox* expbox; }; const QString FilterSideBarWidget::Private::configSearchTextFilterFieldsEntry(QLatin1String("Search Text Filter Fields")); const QString FilterSideBarWidget::Private::configLastShowUntaggedEntry(QLatin1String("Show Untagged")); const QString FilterSideBarWidget::Private::configMatchingConditionEntry(QLatin1String("Matching Condition")); // --------------------------------------------------------------------------------------------------- FilterSideBarWidget::FilterSideBarWidget(QWidget* const parent, TagModel* const tagFilterModel) : DVBox(parent), StateSavingObject(this), d(new Private) { setObjectName(QLatin1String("TagFilter Sidebar")); d->expbox = new DExpanderBox(this); d->expbox->setObjectName(QLatin1String("FilterSideBarWidget Expander")); // -------------------------------------------------------------------------------------------------------- d->textFilter = new TextFilter(d->expbox); d->expbox->addItem(d->textFilter, QIcon::fromTheme(QLatin1String("text-field")), i18n("Text Filter"), QLatin1String("TextFilter"), true); // -------------------------------------------------------------------------------------------------------- d->mimeFilter = new MimeFilter(d->expbox); d->expbox->addItem(d->mimeFilter, QIcon::fromTheme(QLatin1String("folder-open")), i18n("MIME Type Filter"), QLatin1String("TypeMimeFilter"), true); // -------------------------------------------------------------------------------------------------------- d->geolocationFilter = new GeolocationFilter(d->expbox); d->expbox->addItem(d->geolocationFilter, QIcon::fromTheme(QLatin1String("globe")), i18n("Geolocation Filter"), QLatin1String("TypeGeolocationFilter"), true); // -------------------------------------------------------------------------------------------------------- QWidget* const box3 = new QWidget(d->expbox); d->tagFilterModel = tagFilterModel; d->tagFilterView = new TagFilterView(box3, tagFilterModel); d->tagFilterView->setObjectName(QLatin1String("ItemIconViewTagFilterView")); d->tagFilterView->filteredModel()->doNotListTagsWithProperty(TagPropertyName::person()); d->tagFilterView->filteredModel()->setFilterBehavior(AlbumFilterModel::StrictFiltering); d->tagFilterSearchBar = new SearchTextBar(box3, QLatin1String("ItemIconViewTagFilterSearchBar")); d->tagFilterSearchBar->setModel(d->tagFilterView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->tagFilterSearchBar->setFilterModel(d->tagFilterView->albumFilterModel()); const QString notTaggedTitle = i18n("Images Without Tags"); d->withoutTagCheckBox = new QCheckBox(notTaggedTitle, box3); d->withoutTagCheckBox->setWhatsThis(i18n("Show images without a tag.")); d->tagOptionsBtn = new QToolButton(box3); d->tagOptionsBtn->setToolTip( i18n("Tags Matching Condition")); d->tagOptionsBtn->setIcon(QIcon::fromTheme(QLatin1String("configure"))); d->tagOptionsBtn->setPopupMode(QToolButton::InstantPopup); d->tagOptionsBtn->setWhatsThis(i18n("Defines in which way the selected tags are combined " "to filter the images. This also includes the '%1' check box.", notTaggedTitle)); d->tagOptionsMenu = new QMenu(d->tagOptionsBtn); d->tagOrCondAction = d->tagOptionsMenu->addAction(i18n("OR")); d->tagOrCondAction->setCheckable(true); d->tagAndCondAction = d->tagOptionsMenu->addAction(i18n("AND")); d->tagAndCondAction->setCheckable(true); d->tagOptionsBtn->setMenu(d->tagOptionsMenu); QGridLayout* const lay3 = new QGridLayout(box3); lay3->addWidget(d->tagFilterView, 0, 0, 1, 3); lay3->addWidget(d->tagFilterSearchBar, 1, 0, 1, 3); lay3->addWidget(d->withoutTagCheckBox, 2, 0, 1, 1); lay3->addWidget(d->tagOptionsBtn, 2, 2, 1, 1); lay3->setRowStretch(0, 100); lay3->setColumnStretch(1, 10); lay3->setContentsMargins(QMargins()); lay3->setSpacing(0); d->expbox->addItem(box3, QIcon::fromTheme(QLatin1String("tag-assigned")), i18n("Tags Filter"), QLatin1String("TagsFilter"), true); // -------------------------------------------------------------------------------------------------------- QWidget* const box5 = new QWidget(d->expbox); d->faceFilterModel = tagFilterModel; d->faceFilterView = new TagFilterView(box5, tagFilterModel); d->faceFilterView->setObjectName(QLatin1String("ItemIconViewFaceTagFilterView")); d->faceFilterView->filteredModel()->listOnlyTagsWithProperty(TagPropertyName::person()); d->faceFilterView->filteredModel()->setFilterBehavior(AlbumFilterModel::StrictFiltering); d->faceFilterSearchBar = new SearchTextBar(box5, QLatin1String("ItemIconViewFaceTagFilterSearchBar")); d->faceFilterSearchBar->setModel(d->faceFilterView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->faceFilterSearchBar->setFilterModel(d->faceFilterView->albumFilterModel()); const QString notfaceTaggedTitle = i18n("Images Without Face tags"); d->withoutFaceCheckBox = new QCheckBox(notfaceTaggedTitle, box5); d->withoutFaceCheckBox->setWhatsThis(i18n("Show images without a face tag.")); d->faceOptionsBtn = new QToolButton(box5); d->faceOptionsBtn->setToolTip( i18n("Face tags Matching Condition")); d->faceOptionsBtn->setIcon(QIcon::fromTheme(QLatin1String("configure"))); d->faceOptionsBtn->setPopupMode(QToolButton::InstantPopup); d->faceOptionsBtn->setWhatsThis(i18n("Defines in which way the selected tags are combined " "to filter the images. This also includes the '%1' check box.", notfaceTaggedTitle)); d->faceOptionsMenu = new QMenu(d->faceOptionsBtn); d->tagOrCondAction = d->faceOptionsMenu->addAction(i18n("OR")); d->tagOrCondAction->setCheckable(true); d->tagAndCondAction = d->faceOptionsMenu->addAction(i18n("AND")); d->tagAndCondAction->setCheckable(true); d->faceOptionsBtn->setMenu(d->faceOptionsMenu); QGridLayout* const lay5 = new QGridLayout(box5); lay5->addWidget(d->faceFilterView, 0, 0, 1, 3); lay5->addWidget(d->faceFilterSearchBar, 1, 0, 1, 3); lay5->addWidget(d->withoutFaceCheckBox, 2, 0, 1, 1); lay5->addWidget(d->faceOptionsBtn, 2, 2, 1, 1); lay5->setRowStretch(0, 100); lay5->setColumnStretch(1, 10); lay5->setContentsMargins(QMargins()); lay5->setSpacing(0); d->expbox->addItem(box5, QIcon::fromTheme(QLatin1String("tag-assigned")), i18n("Face Tags Filter"), QLatin1String("FaceTagsFilter"), true); // -------------------------------------------------------------------------------------------------------- QWidget* const box4 = new QWidget(d->expbox); d->colorLabelFilter = new ColorLabelFilter(box4); d->pickLabelFilter = new PickLabelFilter(box4); d->ratingFilter = new RatingFilter(box4); QGridLayout* const lay4 = new QGridLayout(box4); lay4->addWidget(d->colorLabelFilter, 0, 0, 1, 3); lay4->addWidget(d->pickLabelFilter, 1, 0, 1, 1); lay4->addWidget(d->ratingFilter, 1, 2, 1, 1); lay4->setColumnStretch(2, 1); lay4->setColumnStretch(3, 10); lay4->setContentsMargins(QMargins()); lay4->setSpacing(0); d->expbox->addItem(box4, QIcon::fromTheme(QLatin1String("folder-favorites")), i18n("Labels Filter"), QLatin1String("LabelsFilter"), true); d->expanderVlay = dynamic_cast(dynamic_cast(d->expbox)->widget()->layout()); d->space = new QWidget(); d->expanderVlay->addWidget(d->space); // -------------------------------------------------------------------------------------------------------- connect(d->expbox, SIGNAL(signalItemExpanded(int,bool)), this, SLOT(slotItemExpanded(int,bool))); connect(d->mimeFilter, SIGNAL(activated(int)), this, SIGNAL(signalMimeTypeFilterChanged(int))); connect(d->geolocationFilter, SIGNAL(signalFilterChanged(ItemFilterSettings::GeolocationCondition)), this, SIGNAL(signalGeolocationFilterChanged(ItemFilterSettings::GeolocationCondition))); connect(d->textFilter, SIGNAL(signalSearchTextFilterSettings(SearchTextFilterSettings)), this, SIGNAL(signalSearchTextFilterChanged(SearchTextFilterSettings))); connect(d->tagFilterView, SIGNAL(checkedTagsChanged(QList,QList)), this, SLOT(slotCheckedTagsChanged(QList,QList))); connect(d->colorLabelFilter, SIGNAL(signalColorLabelSelectionChanged(QList)), this, SLOT(slotColorLabelFilterChanged(QList))); connect(d->pickLabelFilter, SIGNAL(signalPickLabelSelectionChanged(QList)), this, SLOT(slotPickLabelFilterChanged(QList))); connect(d->withoutTagCheckBox, SIGNAL(stateChanged(int)), this, SLOT(slotWithoutTagChanged(int))); connect(d->tagOptionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotTagOptionsTriggered(QAction*))); connect(d->tagOptionsMenu, SIGNAL(aboutToShow()), this, SLOT(slotTagOptionsMenu())); connect(d->ratingFilter, SIGNAL(signalRatingFilterChanged(int,ItemFilterSettings::RatingCondition,bool)), this, SIGNAL(signalRatingFilterChanged(int,ItemFilterSettings::RatingCondition,bool))); } FilterSideBarWidget::~FilterSideBarWidget() { delete d; } void FilterSideBarWidget::slotTagOptionsMenu() { d->tagOrCondAction->setChecked(false); d->tagAndCondAction->setChecked(false); switch (d->tagMatchCond) { case ItemFilterSettings::OrCondition: d->tagOrCondAction->setChecked(true); break; case ItemFilterSettings::AndCondition: d->tagAndCondAction->setChecked(true); break; } } void FilterSideBarWidget::slotItemExpanded(int id, bool b) { if (id == Digikam::TAGS) { d->expanderVlay->setStretchFactor(d->space, b ? 0 : 100); } } void FilterSideBarWidget::setFocusToTextFilter() { d->textFilter->searchTextBar()->setFocus(); } void FilterSideBarWidget::slotFilterMatchesForText(bool match) { d->textFilter->searchTextBar()->slotSearchResult(match); } void FilterSideBarWidget::slotResetFilters() { d->textFilter->reset(); d->mimeFilter->setMimeFilter(MimeFilter::AllFiles); d->geolocationFilter->setGeolocationFilter(ItemFilterSettings::GeolocationNoFilter); d->tagFilterView->slotResetCheckState(); d->withoutTagCheckBox->setChecked(false); d->colorLabelFilter->reset(); d->pickLabelFilter->reset(); d->ratingFilter->setRating(0); d->ratingFilter->setRatingFilterCondition(ItemFilterSettings::GreaterEqualCondition); d->ratingFilter->setExcludeUnratedItems(false); d->tagMatchCond = ItemFilterSettings::OrCondition; } void FilterSideBarWidget::slotTagOptionsTriggered(QAction* action) { if (action) { if (action == d->tagOrCondAction) { d->tagMatchCond = ItemFilterSettings::OrCondition; } else if (action == d->tagAndCondAction) { d->tagMatchCond = ItemFilterSettings::AndCondition; } } filterChanged(); } void FilterSideBarWidget::slotCheckedTagsChanged(const QList& includedTags, const QList& excludedTags) { Q_UNUSED(includedTags); Q_UNUSED(excludedTags); filterChanged(); } void FilterSideBarWidget::slotColorLabelFilterChanged(const QList& list) { Q_UNUSED(list); filterChanged(); } void FilterSideBarWidget::slotPickLabelFilterChanged(const QList& list) { Q_UNUSED(list); filterChanged(); } void FilterSideBarWidget::slotWithoutTagChanged(int newState) { Q_UNUSED(newState); filterChanged(); } void FilterSideBarWidget::filterChanged() { bool showUntagged = d->withoutTagCheckBox->checkState() == Qt::Checked; QList includedTagIds; QList excludedTagIds; QList clTagIds; QList plTagIds; if (!showUntagged || d->tagMatchCond == ItemFilterSettings::OrCondition) { - foreach(TAlbum* tag, d->tagFilterView->getCheckedTags()) + foreach (TAlbum* tag, d->tagFilterView->getCheckedTags()) { if (tag) { includedTagIds << tag->id(); } } - foreach(TAlbum* tag, d->tagFilterView->getPartiallyCheckedTags()) + foreach (TAlbum* tag, d->tagFilterView->getPartiallyCheckedTags()) { if (tag) { excludedTagIds << tag->id(); } } - foreach(TAlbum* tag, d->colorLabelFilter->getCheckedColorLabelTags()) + foreach (TAlbum* tag, d->colorLabelFilter->getCheckedColorLabelTags()) { if (tag) { clTagIds << tag->id(); } } - foreach(TAlbum* tag, d->pickLabelFilter->getCheckedPickLabelTags()) + foreach (TAlbum* tag, d->pickLabelFilter->getCheckedPickLabelTags()) { if (tag) { plTagIds << tag->id(); } } } emit signalTagFilterChanged(includedTagIds, excludedTagIds, d->tagMatchCond, showUntagged, clTagIds, plTagIds); } void FilterSideBarWidget::setConfigGroup(const KConfigGroup& group) { StateSavingObject::setConfigGroup(group); d->tagFilterView->setConfigGroup(group); } void FilterSideBarWidget::doLoadState() { /// @todo mime type and geolocation filter states are not loaded/saved KConfigGroup group = getConfigGroup(); d->expbox->readSettings(group); d->textFilter->setsearchTextFields((SearchTextFilterSettings::TextFilterFields) (group.readEntry(entryName(d->configSearchTextFilterFieldsEntry), (int)SearchTextFilterSettings::All))); d->ratingFilter->setRatingFilterCondition((ItemFilterSettings::RatingCondition) (ApplicationSettings::instance()->getRatingFilterCond())); d->tagMatchCond = (ItemFilterSettings::MatchingCondition) (group.readEntry(entryName(d->configMatchingConditionEntry), (int)ItemFilterSettings::OrCondition)); d->tagFilterView->loadState(); if (d->tagFilterView->isRestoreCheckState()) { d->withoutTagCheckBox->setChecked(group.readEntry(entryName(d->configLastShowUntaggedEntry), false)); } filterChanged(); } void FilterSideBarWidget::doSaveState() { KConfigGroup group = getConfigGroup(); d->expbox->writeSettings(group); group.writeEntry(entryName(d->configSearchTextFilterFieldsEntry), (int)d->textFilter->searchTextFields()); ApplicationSettings::instance()->setRatingFilterCond(d->ratingFilter->ratingFilterCondition()); group.writeEntry(entryName(d->configMatchingConditionEntry), (int)d->tagMatchCond); d->tagFilterView->saveState(); group.writeEntry(entryName(d->configLastShowUntaggedEntry), d->withoutTagCheckBox->isChecked()); group.sync(); } } // namespace Digikam diff --git a/core/app/items/utils/tooltipfiller.cpp b/core/app/items/utils/tooltipfiller.cpp index e4eceafa0e..d5b9aa1cad 100644 --- a/core/app/items/utils/tooltipfiller.cpp +++ b/core/app/items/utils/tooltipfiller.cpp @@ -1,753 +1,753 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-12-10 * Description : album icon view tool tip * * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * 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, 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. * * ============================================================ */ #include "tooltipfiller.h" // Qt includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "collectionlocation.h" #include "collectionmanager.h" #include "applicationsettings.h" #include "album.h" #include "coredbinfocontainers.h" #include "dimgfiltermanager.h" #include "ditemtooltip.h" #include "filteraction.h" #include "iteminfo.h" #include "itempropertiestab.h" #include "colorlabelwidget.h" #include "picklabelwidget.h" #include "albumthumbnailloader.h" #include "thumbnailsize.h" namespace Digikam { QString ToolTipFiller::imageInfoTipContents(const ItemInfo& info) { QString str; ApplicationSettings* const settings = ApplicationSettings::instance(); DToolTipStyleSheet cnt(settings->getToolTipsFont()); ImageCommonContainer commonInfo = info.imageCommonContainer(); ImageMetadataContainer photoInfo = info.imageMetadataContainer(); VideoMetadataContainer videoInfo = info.videoMetadataContainer(); QString tip = cnt.tipHeader; // -- File properties ---------------------------------------------- if (settings->getToolTipsShowFileName() || settings->getToolTipsShowFileDate() || settings->getToolTipsShowFileSize() || settings->getToolTipsShowImageType() || settings->getToolTipsShowImageDim() || settings->getToolTipsShowImageAR()) { tip += cnt.headBeg + i18n("File Properties") + cnt.headEnd; if (settings->getToolTipsShowFileName()) { tip += cnt.cellBeg + i18nc("filename", "Name:") + cnt.cellMid; tip += commonInfo.fileName + cnt.cellEnd; } if (settings->getToolTipsShowFileDate()) { QDateTime modifiedDate = commonInfo.fileModificationDate; str = QLocale().toString(modifiedDate, QLocale::ShortFormat); tip += cnt.cellBeg + i18n("Date:") + cnt.cellMid + str + cnt.cellEnd; } if (settings->getToolTipsShowFileSize()) { tip += cnt.cellBeg + i18n("Size:") + cnt.cellMid; QString localeFileSize = QLocale().toString(commonInfo.fileSize); str = i18n("%1 (%2)", ItemPropertiesTab::humanReadableBytesCount(commonInfo.fileSize), localeFileSize); tip += str + cnt.cellEnd; } if (settings->getToolTipsShowImageType()) { tip += cnt.cellBeg + i18n("Type:") + cnt.cellMid + commonInfo.format + cnt.cellEnd; } if (settings->getToolTipsShowImageDim()) { if (commonInfo.width == 0 || commonInfo.height == 0) { str = i18nc("unknown / invalid image dimension", "Unknown"); } else { QString mpixels; mpixels.setNum(commonInfo.width*commonInfo.height/1000000.0, 'f', 2); str = i18nc("width x height (megapixels Mpx)", "%1x%2 (%3Mpx)", commonInfo.width, commonInfo.height, mpixels); } tip += cnt.cellBeg + i18n("Dimensions:") + cnt.cellMid + str + cnt.cellEnd; } if (settings->getToolTipsShowImageAR()) { if (!ItemPropertiesTab::aspectRatioToString(commonInfo.width, commonInfo.height, str)) { str = i18nc("unknown / invalid image aspect ratio", "Unknown"); } tip += cnt.cellBeg + i18n("Aspect Ratio:") + cnt.cellMid + str + cnt.cellEnd; } } // -- Photograph Info ---------------------------------------------------- if (settings->getToolTipsShowPhotoMake() || settings->getToolTipsShowPhotoLens() || settings->getToolTipsShowPhotoDate() || settings->getToolTipsShowPhotoFocal() || settings->getToolTipsShowPhotoExpo() || settings->getToolTipsShowPhotoMode() || settings->getToolTipsShowPhotoFlash() || settings->getToolTipsShowPhotoWB()) { if (!photoInfo.allFieldsNull || commonInfo.creationDate.isValid()) { QString metaStr; tip += cnt.headBeg + i18n("Photograph Properties") + cnt.headEnd; if (settings->getToolTipsShowPhotoMake()) { ItemPropertiesTab::shortenedMakeInfo(photoInfo.make); ItemPropertiesTab::shortenedModelInfo(photoInfo.model); str = QString::fromUtf8("%1 / %2").arg(photoInfo.make.isEmpty() ? cnt.unavailable : photoInfo.make) .arg(photoInfo.model.isEmpty() ? cnt.unavailable : photoInfo.model); if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Make/Model:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoLens()) { str = photoInfo.lens.isEmpty() ? cnt.unavailable : photoInfo.lens; QString lens = i18nc("camera lens", "Lens:"); if (str.length() > cnt.maxStringLength) { int space = str.lastIndexOf(QLatin1Char(' '), cnt.maxStringLength); if (space == -1) space = cnt.maxStringLength; metaStr += cnt.cellBeg + lens + cnt.cellMid + str.left(space).toHtmlEscaped() + cnt.cellEnd; str = str.mid(space+1); lens = QString(); } if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + lens + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoDate()) { if (commonInfo.creationDate.isValid()) { str = QLocale().toString(commonInfo.creationDate, QLocale::ShortFormat); if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18nc("creation date of the image", "Created:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } else { metaStr += cnt.cellBeg + i18nc("creation date of the image", "Created:") + cnt.cellMid + cnt.unavailable.toHtmlEscaped() + cnt.cellEnd; } } if (settings->getToolTipsShowPhotoFocal()) { str = photoInfo.aperture.isEmpty() ? cnt.unavailable : photoInfo.aperture; if (photoInfo.focalLength35.isEmpty()) { str += QString::fromUtf8(" / %1").arg(photoInfo.focalLength.isEmpty() ? cnt.unavailable : photoInfo.focalLength); } else { str += QString::fromUtf8(" / %1").arg(i18n("%1 (%2)",photoInfo.focalLength,photoInfo.focalLength35)); } if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Aperture/Focal:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoExpo()) { str = QString::fromUtf8("%1 / %2").arg(photoInfo.exposureTime.isEmpty() ? cnt.unavailable : photoInfo.exposureTime) .arg(photoInfo.sensitivity.isEmpty() ? cnt.unavailable : i18n("%1 ISO",photoInfo.sensitivity)); if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Exposure/Sensitivity:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoMode()) { if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) { str = cnt.unavailable; } else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) { str = photoInfo.exposureMode; } else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty()) { str = photoInfo.exposureProgram; } else { str = QString::fromUtf8("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram); } if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Mode/Program:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoFlash()) { str = photoInfo.flashMode.isEmpty() ? cnt.unavailable : photoInfo.flashMode; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18nc("camera flash settings", "Flash:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowPhotoWB()) { str = photoInfo.whiteBalance.isEmpty() ? cnt.unavailable : photoInfo.whiteBalance; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("White Balance:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } tip += metaStr; } } // -- Video Metadata Info ---------------------------------------------------- if (settings->getToolTipsShowVideoAspectRatio() || settings->getToolTipsShowVideoDuration() || settings->getToolTipsShowVideoFrameRate() || settings->getToolTipsShowVideoVideoCodec() || settings->getToolTipsShowVideoAudioBitRate() || settings->getToolTipsShowVideoAudioChannelType() || settings->getToolTipsShowVideoAudioCodec()) { if (!videoInfo.allFieldsNull) { QString metaStr; tip += cnt.headBeg + i18n("Audio/Video Properties") + cnt.headEnd; if (settings->getToolTipsShowVideoAspectRatio()) { str = videoInfo.aspectRatio.isEmpty() ? cnt.unavailable : videoInfo.aspectRatio; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Aspect Ratio:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowVideoDuration()) { QString durationString; bool ok = false; const int durationVal = videoInfo.duration.toInt(&ok); if (ok) { unsigned int r, d, h, m, s, f; r = qAbs(durationVal); d = r / 86400000; r = r % 86400000; h = r / 3600000; r = r % 3600000; m = r / 60000; r = r % 60000; s = r / 1000; f = r % 1000; durationString = QString().sprintf("%d.%02d:%02d:%02d.%03d", d, h, m, s, f); } str = videoInfo.duration.isEmpty() ? cnt.unavailable : durationString; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Duration:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowVideoFrameRate()) { QString frameRateString; bool ok; const double frameRateDouble = videoInfo.frameRate.toDouble(&ok); if (ok) { frameRateString = QLocale().toString(frameRateDouble); } str = videoInfo.frameRate.isEmpty() ? cnt.unavailable : frameRateString; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Frame Rate:") + cnt.cellMid + str.toHtmlEscaped() + i18n(" fps") + cnt.cellEnd; } if (settings->getToolTipsShowVideoVideoCodec()) { str = videoInfo.videoCodec.isEmpty() ? cnt.unavailable : videoInfo.videoCodec; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Video Codec:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowVideoAudioBitRate()) { QString audioBitRateString = str; bool ok; const int audioBitRateInt = videoInfo.audioBitRate.toInt(&ok); if (ok) { audioBitRateString = QLocale().toString(audioBitRateInt); } str = videoInfo.audioBitRate.isEmpty() ? cnt.unavailable : audioBitRateString; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Audio Bit Rate:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowVideoAudioChannelType()) { str = videoInfo.audioChannelType.isEmpty() ? cnt.unavailable : videoInfo.audioChannelType; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Audio Channel Type:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } if (settings->getToolTipsShowVideoAudioCodec()) { str = videoInfo.audioCodec.isEmpty() ? cnt.unavailable : videoInfo.audioCodec; if (str.length() > cnt.maxStringLength) { str = str.left(cnt.maxStringLength-3) + QLatin1String("..."); } metaStr += cnt.cellBeg + i18n("Audio Codec:") + cnt.cellMid + str.toHtmlEscaped() + cnt.cellEnd; } tip += metaStr; } } // -- digiKam properties ------------------------------------------ if (settings->getToolTipsShowAlbumName() || settings->getToolTipsShowTitles() || settings->getToolTipsShowComments() || settings->getToolTipsShowTags() || settings->getToolTipsShowLabelRating()) { tip += cnt.headBeg + i18n("digiKam Properties") + cnt.headEnd; if (settings->getToolTipsShowAlbumName()) { PAlbum* const album = AlbumManager::instance()->findPAlbum(info.albumId()); if (album) { tip += cnt.cellSpecBeg + i18n("Album:") + cnt.cellSpecMid + album->albumPath().remove(0, 1) + cnt.cellSpecEnd; } } if (settings->getToolTipsShowTitles()) { str = info.title(); if (str.isEmpty()) { str = QLatin1String("---"); } tip += cnt.cellSpecBeg + i18nc("title of the file", "Title:") + cnt.cellSpecMid + cnt.breakString(str) + cnt.cellSpecEnd; } if (settings->getToolTipsShowComments()) { str = info.comment(); if (str.isEmpty()) { str = QLatin1String("---"); } tip += cnt.cellSpecBeg + i18nc("caption of the file", "Caption:") + cnt.cellSpecMid + cnt.breakString(str) + cnt.cellSpecEnd; } if (settings->getToolTipsShowTags()) { QStringList tagPaths = AlbumManager::instance()->tagPaths(info.tagIds(), false); tagPaths.sort(); QString tags(i18n("Tags:")); if (tagPaths.isEmpty()) { tip += cnt.cellSpecBeg + tags + cnt.cellSpecMid + QLatin1String("---") + cnt.cellSpecEnd; } else { QString title = tags; QString tagText; for (int i = 0 ; i < tagPaths.size() ; ++i) { tagText = tagPaths.at(i); if (tagText.size() > cnt.maxStringLength) { tagText = cnt.elidedText(tagPaths.at(i), Qt::ElideLeft); } tip += cnt.cellSpecBeg + title + cnt.cellSpecMid + tagText + cnt.cellSpecEnd; title.clear(); } } } if (settings->getToolTipsShowLabelRating()) { str = PickLabelWidget::labelPickName((PickLabel)info.pickLabel()); str += QLatin1String(" / "); str += ColorLabelWidget::labelColorName((ColorLabel)info.colorLabel()); str += QLatin1String(" / "); int rating = info.rating(); if (rating > RatingMin && rating <= RatingMax) { for (int i = 0 ; i < rating ; ++i) { str += QChar(0x2730); str += QLatin1Char(' '); } } else { str += QLatin1String("---"); } tip += cnt.cellSpecBeg + i18n("Labels:") + cnt.cellSpecMid + str + cnt.cellSpecEnd; } } tip += cnt.tipFooter; return tip; } QString ToolTipFiller::albumTipContents(PAlbum* const album, int count) { if (!album || album->isTrashAlbum()) { return QString(); } QString str; ApplicationSettings* const settings = ApplicationSettings::instance(); DToolTipStyleSheet cnt(settings->getToolTipsFont()); QString tip = cnt.tipHeader; if (settings->getToolTipsShowAlbumTitle() || settings->getToolTipsShowAlbumDate() || settings->getToolTipsShowAlbumCollection() || settings->getToolTipsShowAlbumCategory() || settings->getToolTipsShowAlbumCaption()) { tip += cnt.headBeg + i18n("Album Properties") + cnt.headEnd; if (settings->getToolTipsShowAlbumTitle()) { tip += cnt.cellBeg + i18n("Name:") + cnt.cellMid; tip += album->title() + cnt.cellEnd; } if (settings->getShowFolderTreeViewItemsCount()) { tip += cnt.cellBeg + i18n("Items:") + cnt.cellMid; tip += QString::number(count) + cnt.cellEnd; } if (settings->getToolTipsShowAlbumCollection()) { tip += cnt.cellBeg + i18n("Collection:") + cnt.cellMid; CollectionLocation col = CollectionManager::instance()->locationForAlbumRootId(album->albumRootId()); tip += !col.isNull() ? col.label() : QString() + cnt.cellEnd; } if (settings->getToolTipsShowAlbumDate()) { QDate date = album->date(); str = QLocale().toString(date, QLocale::ShortFormat); tip += cnt.cellBeg + i18n("Date:") + cnt.cellMid + str + cnt.cellEnd; } if (settings->getToolTipsShowAlbumCategory()) { str = album->category(); if (str.isEmpty()) { str = QLatin1String("---"); } tip += cnt.cellSpecBeg + i18n("Category:") + cnt.cellSpecMid + cnt.breakString(str) + cnt.cellSpecEnd; } if (settings->getToolTipsShowAlbumCaption()) { str = album->caption(); if (str.isEmpty()) { str = QLatin1String("---"); } tip += cnt.cellSpecBeg + i18n("Caption:") + cnt.cellSpecMid + cnt.breakString(str) + cnt.cellSpecEnd; } if (settings->getToolTipsShowAlbumPreview()) { tip += cnt.cellSpecBeg + i18n("Preview:") + cnt.cellSpecMid + cnt.imageAsBase64(AlbumThumbnailLoader::instance()->getAlbumThumbnailDirectly(album).toImage()) + //cnt.imageAsBase64(AlbumThumbnailLoader::instance()->getAlbumPreviewDirectly(album, ThumbnailSize::Medium)) + cnt.cellSpecEnd; } } tip += cnt.tipFooter; return tip; } QString ToolTipFiller::filterActionTipContents(const FilterAction& action) { if (action.isNull()) { return QString(); } QString str; DToolTipStyleSheet cnt(ApplicationSettings::instance()->getToolTipsFont()); QString tip = cnt.tipHeader; tip += cnt.headBeg + i18n("Filter") + cnt.headEnd; // Displayable name tip += cnt.cellBeg + i18n("Name:") + cnt.cellMid + DImgFilterManager::instance()->i18nDisplayableName(action) + cnt.cellEnd; // Category QString reproducible = QLatin1String("---"); switch (action.category()) { case FilterAction::ReproducibleFilter: reproducible = i18nc("Image filter reproducible: Yes", "Yes"); break; case FilterAction::ComplexFilter: reproducible = i18nc("Image filter reproducible: Partially", "Partially"); break; case FilterAction::DocumentedHistory: reproducible = i18nc("Image filter reproducible: No", "No"); break; default: break; }; tip += cnt.cellBeg + i18n("Reproducible:") + cnt.cellMid + reproducible + cnt.cellEnd; // Description str = action.description(); if (str.isEmpty()) { str = QLatin1String("---"); } tip += cnt.cellSpecBeg + i18nc("Image filter description", "Description:") + cnt.cellSpecMid + cnt.breakString(str) + cnt.cellSpecEnd; // Identifier + version tip += cnt.cellBeg + i18n("Identifier:") + cnt.cellMid + action.identifier() + QLatin1String(" (v") + QString::number(action.version()) + QLatin1String(") ") + cnt.cellEnd; if (action.hasParameters()) { tip += cnt.headBeg + i18n("Technical Parameters") + cnt.headEnd; const QHash& params = action.parameters(); QList keys = params.keys(); std::sort(keys.begin(), keys.end()); - foreach(const QString& key, keys) + foreach (const QString& key, keys) { QHash::const_iterator it; for (it = params.find(key) ; it != params.end() && it.key() == key ; ++it) { if (it.key().isEmpty() || !it.value().isValid()) { continue; } if (it.key().startsWith(QLatin1String("curveData"))) { str = i18n("Binary Data"); } else { str = it.value().toString(); } if (str.length() > cnt.maxStringLength) { str = cnt.elidedText(str, Qt::ElideRight); } QString key = it.key(); QChar first = key.at(0); if (first.isLower()) { key.replace(0, 1, first.toUpper()); } tip += cnt.cellBeg + key + cnt.cellMid + str + cnt.cellEnd; } } } tip += cnt.tipFooter; return tip; } } // namespace Digikam diff --git a/core/app/items/views/itemcategorizedview.cpp b/core/app/items/views/itemcategorizedview.cpp index fb4b20940c..44b9399a66 100644 --- a/core/app/items/views/itemcategorizedview.cpp +++ b/core/app/items/views/itemcategorizedview.cpp @@ -1,740 +1,740 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-04-22 * Description : Qt model-view for items * * Copyright (C) 2009-2012 by Marcel Wiesweg * Copyright (C) 2017 by Simon Frei * * 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, 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. * * ============================================================ */ #include "itemcategorizedview.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "album.h" #include "albummanager.h" #include "coredbfields.h" #include "iccsettings.h" #include "itemalbummodel.h" #include "itemalbumfiltermodel.h" #include "itemcategorydrawer.h" #include "itemdelegate.h" #include "itemdelegateoverlay.h" #include "itemthumbnailmodel.h" #include "itemselectionoverlay.h" #include "itemviewtooltip.h" #include "loadingcacheinterface.h" #include "thumbnailloadthread.h" #include "tooltipfiller.h" #include "itemfacedelegate.h" namespace Digikam { class Q_DECL_HIDDEN ItemCategorizedViewToolTip : public ItemViewToolTip { public: explicit ItemCategorizedViewToolTip(ItemCategorizedView* const view) : ItemViewToolTip(view) { } ItemCategorizedView* view() const { return static_cast(ItemViewToolTip::view()); } protected: virtual QString tipContents() { ItemInfo info = ItemModel::retrieveItemInfo(currentIndex()); return ToolTipFiller::imageInfoTipContents(info); } }; // ------------------------------------------------------------------------------- class Q_DECL_HIDDEN ItemCategorizedView::Private { public: explicit Private() : model(nullptr), filterModel(nullptr), delegate(nullptr), showToolTip(false), scrollToItemId(0), delayedEnterTimer(nullptr), currentMouseEvent(nullptr) { } ItemModel* model; ImageSortFilterModel* filterModel; ItemDelegate* delegate; bool showToolTip; qlonglong scrollToItemId; QUrl unknownCurrentUrl; QTimer* delayedEnterTimer; QMouseEvent* currentMouseEvent; }; // ------------------------------------------------------------------------------- ItemCategorizedView::ItemCategorizedView(QWidget* const parent) : ItemViewCategorized(parent), d(new Private) { setToolTip(new ItemCategorizedViewToolTip(this)); LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)), this, SLOT(slotIccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer))); d->delayedEnterTimer = new QTimer(this); d->delayedEnterTimer->setInterval(10); d->delayedEnterTimer->setSingleShot(true); connect(d->delayedEnterTimer, SIGNAL(timeout()), this, SLOT(slotDelayedEnter())); } ItemCategorizedView::~ItemCategorizedView() { d->delegate->removeAllOverlays(); delete d; } void ItemCategorizedView::installDefaultModels() { ItemAlbumModel* model = new ItemAlbumModel(this); ItemAlbumFilterModel* filterModel = new ItemAlbumFilterModel(this); filterModel->setSourceItemModel(model); filterModel->setSortRole(ItemSortSettings::SortByFileName); filterModel->setCategorizationMode(ItemSortSettings::CategoryByAlbum); filterModel->sort(0); // an initial sorting is necessary // set flags that we want to get dataChanged() signals for model->setWatchFlags(filterModel->suggestedWatchFlags()); setModels(model, filterModel); } void ItemCategorizedView::setModels(ItemModel* model, ImageSortFilterModel* filterModel) { if (d->delegate) { d->delegate->setAllOverlaysActive(false); } if (d->filterModel) { disconnect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); disconnect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged())); } if (d->model) { disconnect(d->model, SIGNAL(imageInfosAdded(QList)), this, SLOT(slotItemInfosAdded())); } d->model = model; d->filterModel = filterModel; setModel(d->filterModel); connect(d->filterModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); connect(d->filterModel, SIGNAL(layoutChanged()), this, SLOT(layoutWasChanged()), Qt::QueuedConnection); connect(d->model, SIGNAL(imageInfosAdded(QList)), this, SLOT(slotItemInfosAdded())); emit modelChanged(); if (d->delegate) { d->delegate->setAllOverlaysActive(true); } } ItemModel* ItemCategorizedView::imageModel() const { return d->model; } ImageSortFilterModel* ItemCategorizedView::imageSortFilterModel() const { return d->filterModel; } ItemFilterModel* ItemCategorizedView::imageFilterModel() const { return d->filterModel->imageFilterModel(); } ItemThumbnailModel* ItemCategorizedView::imageThumbnailModel() const { return qobject_cast(d->model); } ItemAlbumModel* ItemCategorizedView::imageAlbumModel() const { return qobject_cast(d->model); } ItemAlbumFilterModel* ItemCategorizedView::imageAlbumFilterModel() const { return qobject_cast(d->filterModel->imageFilterModel()); } QSortFilterProxyModel* ItemCategorizedView::filterModel() const { return d->filterModel; } ItemDelegate* ItemCategorizedView::delegate() const { return d->delegate; } void ItemCategorizedView::setItemDelegate(ItemDelegate* delegate) { ThumbnailSize oldSize = thumbnailSize(); ItemDelegate* oldDelegate = d->delegate; if (oldDelegate) { hideIndexNotification(); d->delegate->setAllOverlaysActive(false); d->delegate->setViewOnAllOverlays(nullptr); // Note: Be precise, no wildcard disconnect! disconnect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); disconnect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } d->delegate = delegate; d->delegate->setThumbnailSize(oldSize); if (oldDelegate) { d->delegate->setSpacing(oldDelegate->spacing()); } ItemViewCategorized::setItemDelegate(d->delegate); setCategoryDrawer(d->delegate->categoryDrawer()); updateDelegateSizes(); d->delegate->setViewOnAllOverlays(this); d->delegate->setAllOverlaysActive(true); connect(d->delegate, SIGNAL(requestNotification(QModelIndex,QString)), this, SLOT(showIndexNotification(QModelIndex,QString))); connect(d->delegate, SIGNAL(hideNotification()), this, SLOT(hideIndexNotification())); } Album* ItemCategorizedView::currentAlbum() const { ItemAlbumModel* const albumModel = imageAlbumModel(); /** TODO: Change to QList return type **/ if (albumModel && !(albumModel->currentAlbums().isEmpty())) { return albumModel->currentAlbums().first(); } return nullptr; } ItemInfo ItemCategorizedView::currentInfo() const { return imageInfo(currentIndex()); } QUrl ItemCategorizedView::currentUrl() const { return currentInfo().fileUrl(); } ItemInfo ItemCategorizedView::imageInfo(const QModelIndex& index) const { return d->filterModel->imageInfo(index); } ItemInfoList ItemCategorizedView::imageInfos(const QList& indexes) const { return ItemInfoList(d->filterModel->imageInfos(indexes)); } ItemInfoList ItemCategorizedView::allItemInfos() const { return ItemInfoList(d->filterModel->imageInfosSorted()); } QList ItemCategorizedView::allUrls() const { return allItemInfos().toImageUrlList(); } ItemInfoList ItemCategorizedView::selectedItemInfos() const { return imageInfos(selectedIndexes()); } ItemInfoList ItemCategorizedView::selectedItemInfosCurrentFirst() const { QModelIndexList indexes = selectedIndexes(); const QModelIndex current = currentIndex(); if (!indexes.isEmpty()) { if (indexes.first() != current) { if (indexes.removeOne(current)) { indexes.prepend(current); } } } return imageInfos(indexes); } void ItemCategorizedView::toIndex(const QUrl& url) { ItemViewCategorized::toIndex(d->filterModel->indexForPath(url.toLocalFile())); } QModelIndex ItemCategorizedView::indexForInfo(const ItemInfo& info) const { return d->filterModel->indexForItemInfo(info); } ItemInfo ItemCategorizedView::nextInOrder(const ItemInfo& startingPoint, int nth) { QModelIndex index = d->filterModel->indexForItemInfo(startingPoint); if (!index.isValid()) { return ItemInfo(); } return imageInfo(d->filterModel->index(index.row() + nth, 0, QModelIndex())); } QModelIndex ItemCategorizedView::nextIndexHint(const QModelIndex& anchor, const QItemSelectionRange& removed) const { QModelIndex hint = ItemViewCategorized::nextIndexHint(anchor, removed); ItemInfo info = imageInfo(anchor); //qCDebug(DIGIKAM_GENERAL_LOG) << "Having initial hint" << hint << "for" << anchor << d->model->numberOfIndexesForItemInfo(info); // Fixes a special case of multiple (face) entries for the same image. // If one is removed, any entry of the same image shall be preferred. if (d->model->numberOfIndexesForItemInfo(info) > 1) { // The hint is for a different info, but we may have a hint for the same info if (info != imageInfo(hint)) { int minDiff = d->filterModel->rowCount(); QList indexesForItemInfo = d->filterModel->mapListFromSource(d->model->indexesForItemInfo(info)); - foreach(const QModelIndex& index, indexesForItemInfo) + foreach (const QModelIndex& index, indexesForItemInfo) { if (index == anchor || !index.isValid() || removed.contains(index)) { continue; } int distance = qAbs(index.row() - anchor.row()); if (distance < minDiff) { minDiff = distance; hint = index; //qCDebug(DIGIKAM_GENERAL_LOG) << "Chose index" << hint << "at distance" << minDiff << "to" << anchor; } } } } return hint; } void ItemCategorizedView::openAlbum(const QList& albums) { ItemAlbumModel* const albumModel = imageAlbumModel(); if (albumModel) { albumModel->openAlbum(albums); } } ThumbnailSize ItemCategorizedView::thumbnailSize() const { /* ItemThumbnailModel* const thumbModel = imageThumbnailModel(); if (thumbModel) return thumbModel->thumbnailSize(); */ if (d->delegate) { return d->delegate->thumbnailSize(); } return ThumbnailSize(); } void ItemCategorizedView::setThumbnailSize(int size) { setThumbnailSize(ThumbnailSize(size)); } void ItemCategorizedView::setThumbnailSize(const ThumbnailSize& s) { // we abuse this pair of method calls to restore scroll position layoutAboutToBeChanged(); d->delegate->setThumbnailSize(s); layoutWasChanged(); } void ItemCategorizedView::setCurrentWhenAvailable(qlonglong imageId) { d->scrollToItemId = imageId; } void ItemCategorizedView::setCurrentUrlWhenAvailable(const QUrl& url) { if (url.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); return; } QString path = url.toLocalFile(); QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { d->unknownCurrentUrl = url; return; } clearSelection(); setCurrentIndex(index); d->unknownCurrentUrl.clear(); } void ItemCategorizedView::setCurrentUrl(const QUrl& url) { if (url.isEmpty()) { clearSelection(); setCurrentIndex(QModelIndex()); return; } QString path = url.toLocalFile(); QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { return; } clearSelection(); setCurrentIndex(index); } void ItemCategorizedView::setCurrentInfo(const ItemInfo& info) { QModelIndex index = d->filterModel->indexForItemInfo(info); clearSelection(); setCurrentIndex(index); } void ItemCategorizedView::setSelectedUrls(const QList& urlList) { QItemSelection mySelection; for (QList::const_iterator it = urlList.constBegin(); it!=urlList.constEnd(); ++it) { const QString path = it->toLocalFile(); const QModelIndex index = d->filterModel->indexForPath(path); if (!index.isValid()) { qCWarning(DIGIKAM_GENERAL_LOG) << "no QModelIndex found for" << *it; } else { // TODO: is there a better way? mySelection.select(index, index); } } clearSelection(); selectionModel()->select(mySelection, QItemSelectionModel::Select); } void ItemCategorizedView::setSelectedItemInfos(const QList& infos) { QItemSelection mySelection; - foreach(const ItemInfo& info, infos) + foreach (const ItemInfo& info, infos) { QModelIndex index = d->filterModel->indexForItemInfo(info); mySelection.select(index, index); } selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect); } void ItemCategorizedView::hintAt(const ItemInfo& info) { if (info.isNull()) { return; } QModelIndex index = d->filterModel->indexForItemInfo(info); if (!index.isValid()) { return; } selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); scrollTo(index); } void ItemCategorizedView::addOverlay(ItemDelegateOverlay* overlay, ItemDelegate* delegate) { if (!delegate) { delegate = d->delegate; } delegate->installOverlay(overlay); if (delegate == d->delegate) { overlay->setView(this); overlay->setActive(true); } } void ItemCategorizedView::removeOverlay(ItemDelegateOverlay* overlay) { ItemDelegate* delegate = dynamic_cast(overlay->delegate()); if (delegate) { delegate->removeOverlay(overlay); } overlay->setView(nullptr); } void ItemCategorizedView::updateGeometries() { ItemViewCategorized::updateGeometries(); d->delayedEnterTimer->start(); } void ItemCategorizedView::slotDelayedEnter() { // re-emit entered() for index under mouse (after layout). QModelIndex mouseIndex = indexAt(mapFromGlobal(QCursor::pos())); if (mouseIndex.isValid()) { emit DCategorizedView::entered(mouseIndex); } } void ItemCategorizedView::addSelectionOverlay(ItemDelegate* delegate) { addOverlay(new ItemSelectionOverlay(this), delegate); } void ItemCategorizedView::scrollToStoredItem() { if (d->scrollToItemId) { if (d->model->hasImage(d->scrollToItemId)) { QModelIndex index = d->filterModel->indexForImageId(d->scrollToItemId); setCurrentIndex(index); scrollToRelaxed(index, QAbstractItemView::PositionAtCenter); d->scrollToItemId = 0; } } } void ItemCategorizedView::slotItemInfosAdded() { if (d->scrollToItemId) { scrollToStoredItem(); } else if (!d->unknownCurrentUrl.isEmpty()) { QTimer::singleShot(100, this, SLOT(slotCurrentUrlTimer())); } } void ItemCategorizedView::slotCurrentUrlTimer() { setCurrentUrl(d->unknownCurrentUrl); d->unknownCurrentUrl.clear(); } void ItemCategorizedView::slotFileChanged(const QString& filePath) { QModelIndex index = d->filterModel->indexForPath(filePath); if (index.isValid()) { update(index); } } void ItemCategorizedView::indexActivated(const QModelIndex& index, Qt::KeyboardModifiers modifiers) { ItemInfo info = imageInfo(index); if (!info.isNull()) { activated(info, modifiers); emit imageActivated(info); } } void ItemCategorizedView::currentChanged(const QModelIndex& index, const QModelIndex& previous) { ItemViewCategorized::currentChanged(index, previous); emit currentChanged(imageInfo(index)); } void ItemCategorizedView::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems) { ItemViewCategorized::selectionChanged(selectedItems, deselectedItems); if (!selectedItems.isEmpty()) { emit selected(imageInfos(selectedItems.indexes())); } if (!deselectedItems.isEmpty()) { emit deselected(imageInfos(deselectedItems.indexes())); } } Album* ItemCategorizedView::albumAt(const QPoint& pos) const { if (imageFilterModel()->imageSortSettings().categorizationMode == ItemSortSettings::CategoryByAlbum) { QModelIndex categoryIndex = indexForCategoryAt(pos); if (categoryIndex.isValid()) { int albumId = categoryIndex.data(ItemFilterModel::CategoryAlbumIdRole).toInt(); return AlbumManager::instance()->findPAlbum(albumId); } } return currentAlbum(); } void ItemCategorizedView::activated(const ItemInfo&, Qt::KeyboardModifiers) { // implemented in subclass } void ItemCategorizedView::showContextMenuOnIndex(QContextMenuEvent* event, const QModelIndex& index) { showContextMenuOnInfo(event, imageInfo(index)); } void ItemCategorizedView::showContextMenuOnInfo(QContextMenuEvent*, const ItemInfo&) { // implemented in subclass } void ItemCategorizedView::paintEvent(QPaintEvent* e) { // We want the thumbnails to be loaded in order. ItemThumbnailModel* const thumbModel = imageThumbnailModel(); if (thumbModel) { QModelIndexList indexesToThumbnail = imageFilterModel()->mapListToSource(categorizedIndexesIn(viewport()->rect())); d->delegate->prepareThumbnails(thumbModel, indexesToThumbnail); } ItemViewCategorized::paintEvent(e); } QItemSelectionModel* ItemCategorizedView::getSelectionModel() const { return selectionModel(); } AbstractItemDragDropHandler* ItemCategorizedView::dragDropHandler() const { return d->model->dragDropHandler(); } void ItemCategorizedView::slotIccSettingsChanged(const ICCSettingsContainer&, const ICCSettingsContainer&) { viewport()->update(); } } // namespace Digikam diff --git a/core/app/main/digikamapp_setup.cpp b/core/app/main/digikamapp_setup.cpp index c7dd099c9e..7d62435e55 100644 --- a/core/app/main/digikamapp_setup.cpp +++ b/core/app/main/digikamapp_setup.cpp @@ -1,1066 +1,1066 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2002-16-10 * Description : main digiKam interface implementation - Internal setup * * Copyright (C) 2002-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "digikamapp_p.h" namespace Digikam { void DigikamApp::setupView() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Initializing Main View...")); } d->view = new ItemIconView(this, d->modelCollection); setCentralWidget(d->view); d->view->applySettings(); } void DigikamApp::setupViewConnections() { connect(d->view, SIGNAL(signalAlbumSelected(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->view, SIGNAL(signalSelectionChanged(int)), this, SLOT(slotSelectionChanged(int))); connect(d->view, SIGNAL(signalTrashSelectionChanged(QString)), this, SLOT(slotTrashSelectionChanged(QString))); connect(d->view, SIGNAL(signalImageSelected(ItemInfoList,ItemInfoList)), this, SLOT(slotImageSelected(ItemInfoList,ItemInfoList))); connect(d->view, SIGNAL(signalSwitchedToPreview()), this, SLOT(slotSwitchedToPreview())); connect(d->view, SIGNAL(signalSwitchedToIconView()), this, SLOT(slotSwitchedToIconView())); connect(d->view, SIGNAL(signalSwitchedToMapView()), this, SLOT(slotSwitchedToMapView())); connect(d->view, SIGNAL(signalSwitchedToTableView()), this, SLOT(slotSwitchedToTableView())); connect(d->view, SIGNAL(signalSwitchedToTrashView()), this, SLOT(slotSwitchedToTrashView())); } void DigikamApp::setupStatusBar() { d->statusLabel = new DAdjustableLabel(statusBar()); d->statusLabel->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); statusBar()->addWidget(d->statusLabel, 80); //------------------------------------------------------------------------------ d->metadataStatusBar = new MetadataStatusBar(statusBar()); statusBar()->addWidget(d->metadataStatusBar, 50); //------------------------------------------------------------------------------ d->filterStatusBar = new FilterStatusBar(statusBar()); statusBar()->addWidget(d->filterStatusBar, 50); d->view->connectIconViewFilter(d->filterStatusBar); //------------------------------------------------------------------------------ ProgressView* const view = new ProgressView(statusBar(), this); view->hide(); StatusbarProgressWidget* const littleProgress = new StatusbarProgressWidget(view, statusBar()); littleProgress->show(); statusBar()->addPermanentWidget(littleProgress); //------------------------------------------------------------------------------ d->zoomBar = new DZoomBar(statusBar()); d->zoomBar->setZoomToFitAction(d->zoomFitToWindowAction); d->zoomBar->setZoomTo100Action(d->zoomTo100percents); d->zoomBar->setZoomPlusAction(d->zoomPlusAction); d->zoomBar->setZoomMinusAction(d->zoomMinusAction); d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); statusBar()->addPermanentWidget(d->zoomBar); //------------------------------------------------------------------------------ connect(d->zoomBar, SIGNAL(signalZoomSliderChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(this, SIGNAL(signalWindowHasMoved()), d->zoomBar, SLOT(slotUpdateTrackerPos())); connect(d->zoomBar, SIGNAL(signalZoomValueEdited(double)), d->view, SLOT(setZoomFactor(double))); connect(d->view, SIGNAL(signalZoomChanged(double)), this, SLOT(slotZoomChanged(double))); connect(d->view, SIGNAL(signalThumbSizeChanged(int)), this, SLOT(slotThumbSizeChanged(int))); } void DigikamApp::setupActions() { KActionCollection* const ac = actionCollection(); d->solidCameraActionGroup = new QActionGroup(this); connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidCamera(QAction*))); d->solidUsmActionGroup = new QActionGroup(this); connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidUsmDevice(QAction*))); d->manualCameraActionGroup = new QActionGroup(this); connect(d->manualCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenManualCamera(QAction*))); // ----------------------------------------------------------------- d->backwardActionMenu = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("go-previous")), i18n("&Back"), this); d->backwardActionMenu->setEnabled(false); ac->addAction(QLatin1String("album_back"), d->backwardActionMenu); ac->setDefaultShortcut(d->backwardActionMenu, Qt::ALT + Qt::Key_Left); connect(d->backwardActionMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowBackwardMenu())); connect(d->backwardActionMenu, &QAction::triggered, this, [this]() { d->view->slotAlbumHistoryBack(1); }); // ----------------------------------------------------------------- d->forwardActionMenu = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("Forward"), this); d->forwardActionMenu->setEnabled(false); ac->addAction(QLatin1String("album_forward"), d->forwardActionMenu); ac->setDefaultShortcut(d->forwardActionMenu, Qt::ALT + Qt::Key_Right); connect(d->forwardActionMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowForwardMenu())); connect(d->forwardActionMenu, &QAction::triggered, this, [this]() { d->view->slotAlbumHistoryForward(1); }); // ----------------------------------------------------------------- d->refreshAction = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Refresh"), this); d->refreshAction->setWhatsThis(i18n("Refresh the current contents.")); connect(d->refreshAction, SIGNAL(triggered()), d->view, SLOT(slotRefresh())); ac->addAction(QLatin1String("view_refresh"), d->refreshAction); ac->setDefaultShortcut(d->refreshAction, Qt::Key_F5); // ----------------------------------------------------------------- - foreach(SidebarWidget* const leftWidget, d->view->leftSidebarWidgets()) + foreach (SidebarWidget* const leftWidget, d->view->leftSidebarWidgets()) { QString actionName = QLatin1String("browse_") + leftWidget->objectName() .remove(QLatin1Char(' ')) .remove(QLatin1String("Sidebar")) .remove(QLatin1String("FolderView")) .remove(QLatin1String("View")).toLower(); qCDebug(DIGIKAM_GENERAL_LOG) << actionName; QAction* const action = new QAction(leftWidget->getIcon(), leftWidget->getCaption(), this); ac->addAction(actionName, action); ac->setDefaultShortcut(action, QKeySequence(leftWidget->property("Shortcut").toInt())); connect(action, &QAction::triggered, this, [this, leftWidget]() { d->view->slotLeftSideBarActivate(leftWidget); }); } // ----------------------------------------------------------------- d->newAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("&New..."), this); d->newAction->setWhatsThis(i18n("Creates a new empty Album in the collection.")); connect(d->newAction, SIGNAL(triggered()), d->view, SLOT(slotNewAlbum())); ac->addAction(QLatin1String("album_new"), d->newAction); ac->setDefaultShortcuts(d->newAction, QList() << Qt::CTRL + Qt::Key_N); // ----------------------------------------------------------------- d->moveSelectionToAlbumAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("&Move to Album..."), this); d->moveSelectionToAlbumAction->setWhatsThis(i18n("Move selected images into an album.")); connect(d->moveSelectionToAlbumAction, SIGNAL(triggered()), d->view, SLOT(slotMoveSelectionToAlbum())); ac->addAction(QLatin1String("move_selection_to_album"), d->moveSelectionToAlbumAction); // ----------------------------------------------------------------- d->deleteAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Album"), this); connect(d->deleteAction, SIGNAL(triggered()), d->view, SLOT(slotDeleteAlbum())); ac->addAction(QLatin1String("album_delete"), d->deleteAction); // ----------------------------------------------------------------- d->renameAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Rename..."), this); connect(d->renameAction, SIGNAL(triggered()), d->view, SLOT(slotRenameAlbum())); ac->addAction(QLatin1String("album_rename"), d->renameAction); ac->setDefaultShortcut(d->renameAction, Qt::SHIFT + Qt::Key_F2); // ----------------------------------------------------------------- d->propsEditAction = new QAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Properties"), this); d->propsEditAction->setWhatsThis(i18n("Edit album properties and collection information.")); connect(d->propsEditAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumPropsEdit())); ac->addAction(QLatin1String("album_propsEdit"), d->propsEditAction); ac->setDefaultShortcut(d->propsEditAction, Qt::ALT + Qt::Key_Return); // ----------------------------------------------------------------- d->writeAlbumMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Write Metadata to Files"), this); d->writeAlbumMetadataAction->setWhatsThis(i18n("Updates metadata of files in the current " "album with the contents of digiKam database " "(file metadata will be overwritten with data from " "the database).")); connect(d->writeAlbumMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumWriteMetadata())); ac->addAction(QLatin1String("album_write_metadata"), d->writeAlbumMetadataAction); // ----------------------------------------------------------------- d->readAlbumMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("edit-redo")), i18n("Reread Metadata From Files"), this); d->readAlbumMetadataAction->setWhatsThis(i18n("Updates the digiKam database from the metadata " "of the files in the current album " "(information in the database will be overwritten with data from " "the files' metadata).")); connect(d->readAlbumMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumReadMetadata())); ac->addAction(QLatin1String("album_read_metadata"), d->readAlbumMetadataAction); // ----------------------------------------------------------------- d->openInFileManagerAction = new QAction(QIcon::fromTheme(QLatin1String("folder-open")), i18n("Open in File Manager"), this); connect(d->openInFileManagerAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumOpenInFileManager())); ac->addAction(QLatin1String("album_openinfilemanager"), d->openInFileManagerAction); // ----------------------------------------------------------- d->openTagMngrAction = new QAction(QIcon::fromTheme(QLatin1String("tag")), i18n("Tag Manager"), this); connect(d->openTagMngrAction, SIGNAL(triggered()), d->view, SLOT(slotOpenTagsManager())); ac->addAction(QLatin1String("open_tag_mngr"), d->openTagMngrAction); // ----------------------------------------------------------- d->newTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18nc("new tag", "N&ew..."), this); connect(d->newTagAction, SIGNAL(triggered()), d->view, SLOT(slotNewTag())); ac->addAction(QLatin1String("tag_new"), d->newTagAction); // ----------------------------------------------------------- d->editTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-properties")), i18n("Properties"), this); connect(d->editTagAction, SIGNAL(triggered()), d->view, SLOT(slotEditTag())); ac->addAction(QLatin1String("tag_edit"), d->editTagAction); // ----------------------------------------------------------- d->deleteTagAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete"), this); connect(d->deleteTagAction, SIGNAL(triggered()), d->view, SLOT(slotDeleteTag())); ac->addAction(QLatin1String("tag_delete"), d->deleteTagAction); // ----------------------------------------------------------- d->assignTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18n("Assign Tag"), this); connect(d->assignTagAction, SIGNAL(triggered()), d->view, SLOT(slotAssignTag())); ac->addAction(QLatin1String("tag_assign"), d->assignTagAction); ac->setDefaultShortcut(d->assignTagAction, Qt::Key_T); // ----------------------------------------------------------- d->imageViewSelectionAction = new KSelectAction(QIcon::fromTheme(QLatin1String("view-preview")), i18n("Views"), this); ac->addAction(QLatin1String("view_selection"), d->imageViewSelectionAction); d->imageIconViewAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-icons")), i18nc("@action Go to thumbnails (icon) view", "Thumbnails"), this); d->imageIconViewAction->setCheckable(true); ac->addAction(QLatin1String("icon_view"), d->imageIconViewAction); connect(d->imageIconViewAction, SIGNAL(triggered()), d->view, SLOT(slotIconView())); d->imageViewSelectionAction->addAction(d->imageIconViewAction); d->imagePreviewAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")), i18nc("View the selected image", "Preview"), this); d->imagePreviewAction->setCheckable(true); ac->addAction(QLatin1String("image_view"), d->imagePreviewAction); ac->setDefaultShortcut(d->imagePreviewAction, Qt::Key_F3); connect(d->imagePreviewAction, SIGNAL(triggered()), d->view, SLOT(slotImagePreview())); d->imageViewSelectionAction->addAction(d->imagePreviewAction); #ifdef HAVE_MARBLE d->imageMapViewAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18nc("@action Switch to map view", "Map"), this); d->imageMapViewAction->setCheckable(true); ac->addAction(QLatin1String("map_view"), d->imageMapViewAction); connect(d->imageMapViewAction, SIGNAL(triggered()), d->view, SLOT(slotMapWidgetView())); d->imageViewSelectionAction->addAction(d->imageMapViewAction); #endif // HAVE_MARBLE d->imageTableViewAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-details")), i18nc("@action Switch to table view", "Table"), this); d->imageTableViewAction->setCheckable(true); ac->addAction(QLatin1String("table_view"), d->imageTableViewAction); connect(d->imageTableViewAction, SIGNAL(triggered()), d->view, SLOT(slotTableView())); d->imageViewSelectionAction->addAction(d->imageTableViewAction); // ----------------------------------------------------------- d->imageViewAction = new QAction(QIcon::fromTheme(QLatin1String("quickopen-file")), i18n("Open..."), this); d->imageViewAction->setWhatsThis(i18n("Open the selected item.")); connect(d->imageViewAction, SIGNAL(triggered()), d->view, SLOT(slotImageEdit())); ac->addAction(QLatin1String("image_edit"), d->imageViewAction); ac->setDefaultShortcut(d->imageViewAction, Qt::Key_F4); d->openWithAction = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-filetype-association")), i18n("Open With Default Application"), this); d->openWithAction->setWhatsThis(i18n("Open the selected item with default assigned application.")); connect(d->openWithAction, SIGNAL(triggered()), d->view, SLOT(slotFileWithDefaultApplication())); ac->addAction(QLatin1String("open_with_default_application"), d->openWithAction); ac->setDefaultShortcut(d->openWithAction, Qt::CTRL + Qt::Key_F4); d->ieAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Image Editor"), this); d->ieAction->setWhatsThis(i18n("Open the image editor.")); connect(d->ieAction, SIGNAL(triggered()), d->view, SLOT(slotEditor())); ac->addAction(QLatin1String("imageeditor"), d->ieAction); // ----------------------------------------------------------- d->ltAction = new QAction(QIcon::fromTheme(QLatin1String("lighttable")), i18n("Light Table"), this); connect(d->ltAction, SIGNAL(triggered()), d->view, SLOT(slotLightTable())); ac->addAction(QLatin1String("light_table"), d->ltAction); ac->setDefaultShortcut(d->ltAction, Qt::SHIFT + Qt::Key_L); d->imageLightTableAction = new QAction(QIcon::fromTheme(QLatin1String("lighttable")), i18n("Place onto Light Table"), this); d->imageLightTableAction->setWhatsThis(i18n("Place the selected items on the light table thumbbar.")); connect(d->imageLightTableAction, SIGNAL(triggered()), d->view, SLOT(slotImageLightTable())); ac->addAction(QLatin1String("image_lighttable"), d->imageLightTableAction); ac->setDefaultShortcut(d->imageLightTableAction, Qt::CTRL + Qt::Key_L); d->imageAddLightTableAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add to Light Table"), this); d->imageAddLightTableAction->setWhatsThis(i18n("Add selected items to the light table thumbbar.")); connect(d->imageAddLightTableAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToLightTable())); ac->addAction(QLatin1String("image_add_to_lighttable"), d->imageAddLightTableAction); ac->setDefaultShortcut(d->imageAddLightTableAction, Qt::CTRL + Qt::SHIFT + Qt::Key_L); // ----------------------------------------------------------- d->bqmAction = new QAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Batch Queue Manager"), this); connect(d->bqmAction, SIGNAL(triggered()), d->view, SLOT(slotQueueMgr())); ac->addAction(QLatin1String("queue_manager"), d->bqmAction); ac->setDefaultShortcut(d->bqmAction, Qt::SHIFT + Qt::Key_B); d->imageAddCurrentQueueAction = new QAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Add to Current Queue"), this); d->imageAddCurrentQueueAction->setWhatsThis(i18n("Add selected items to current queue from batch manager.")); connect(d->imageAddCurrentQueueAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToCurrentQueue())); ac->addAction(QLatin1String("image_add_to_current_queue"), d->imageAddCurrentQueueAction); ac->setDefaultShortcut(d->imageAddCurrentQueueAction, Qt::CTRL + Qt::Key_B); d->imageAddNewQueueAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add to New Queue"), this); d->imageAddNewQueueAction->setWhatsThis(i18n("Add selected items to a new queue from batch manager.")); connect(d->imageAddNewQueueAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToNewQueue())); ac->addAction(QLatin1String("image_add_to_new_queue"), d->imageAddNewQueueAction); ac->setDefaultShortcut(d->imageAddNewQueueAction, Qt::CTRL + Qt::SHIFT + Qt::Key_B); // ----------------------------------------------------------------- d->quickImportMenu->setTitle(i18nc("@action Import photos from camera", "Import")); d->quickImportMenu->setIcon(QIcon::fromTheme(QLatin1String("camera-photo"))); ac->addAction(QLatin1String("import_auto"), d->quickImportMenu->menuAction()); // ----------------------------------------------------------------- d->imageWriteMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Write Metadata to Selected Files"), this); d->imageWriteMetadataAction->setWhatsThis(i18n("Updates metadata of files in the current " "album with the contents of digiKam database " "(file metadata will be overwritten with data from " "the database).")); connect(d->imageWriteMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotImageWriteMetadata())); ac->addAction(QLatin1String("image_write_metadata"), d->imageWriteMetadataAction); // ----------------------------------------------------------------- d->imageReadMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("edit-redo")), i18n("Reread Metadata From Selected Files"), this); d->imageReadMetadataAction->setWhatsThis(i18n("Updates the digiKam database from the metadata " "of the files in the current album " "(information in the database will be overwritten with data from " "the files' metadata).")); connect(d->imageReadMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotImageReadMetadata())); ac->addAction(QLatin1String("image_read_metadata"), d->imageReadMetadataAction); // ----------------------------------------------------------- d->imageScanForFacesAction = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")), i18n("Scan for Faces"), this); connect(d->imageScanForFacesAction, SIGNAL(triggered()), d->view, SLOT(slotImageScanForFaces())); ac->addAction(QLatin1String("image_scan_for_faces"), d->imageScanForFacesAction); // ----------------------------------------------------------- d->imageFindSimilarAction = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")), i18n("Find Similar..."), this); d->imageFindSimilarAction->setWhatsThis(i18n("Find similar images using selected item as reference.")); connect(d->imageFindSimilarAction, SIGNAL(triggered()), d->view, SLOT(slotImageFindSimilar())); ac->addAction(QLatin1String("image_find_similar"), d->imageFindSimilarAction); // ----------------------------------------------------------- d->imageRenameAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Rename..."), this); d->imageRenameAction->setWhatsThis(i18n("Change the filename of the currently selected item.")); connect(d->imageRenameAction, SIGNAL(triggered()), d->view, SLOT(slotImageRename())); ac->addAction(QLatin1String("image_rename"), d->imageRenameAction); ac->setDefaultShortcut(d->imageRenameAction, Qt::Key_F2); // ----------------------------------------------------------- // Pop up dialog to ask user whether to move to trash d->imageDeleteAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash-full")), i18nc("Non-pluralized", "Move to Trash"), this); connect(d->imageDeleteAction, SIGNAL(triggered()), d->view, SLOT(slotImageDelete())); ac->addAction(QLatin1String("image_delete"), d->imageDeleteAction); ac->setDefaultShortcut(d->imageDeleteAction, Qt::Key_Delete); // ----------------------------------------------------------- // Pop up dialog to ask user whether to permanently delete // FIXME: This action is never used?? How can someone delete a album directly, without moving it to the trash first? // This is especially important when deleting from a different partition or from a net source. // Also note that we use the wrong icon for the default album delete action, which should have a trashcan icon instead // of a red cross, it confuses users. d->imageDeletePermanentlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Permanently"), this); connect(d->imageDeletePermanentlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageDeletePermanently())); ac->addAction(QLatin1String("image_delete_permanently"), d->imageDeletePermanentlyAction); ac->setDefaultShortcut(d->imageDeletePermanentlyAction, Qt::SHIFT + Qt::Key_Delete); // ----------------------------------------------------------- // These two actions are hidden, no menu entry, no toolbar entry, no shortcut. // Power users may add them. d->imageDeletePermanentlyDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete permanently without confirmation"), this); connect(d->imageDeletePermanentlyDirectlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageDeletePermanentlyDirectly())); ac->addAction(QLatin1String("image_delete_permanently_directly"), d->imageDeletePermanentlyDirectlyAction); // ----------------------------------------------------------- d->imageTrashDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash-full")), i18n("Move to trash without confirmation"), this); connect(d->imageTrashDirectlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageTrashDirectly())); ac->addAction(QLatin1String("image_trash_directly"), d->imageTrashDirectlyAction); // ----------------------------------------------------------------- d->albumSortAction = new KSelectAction(i18n("&Sort Albums"), this); d->albumSortAction->setWhatsThis(i18n("Sort Albums in tree-view.")); connect(d->albumSortAction, SIGNAL(triggered(int)), d->view, SLOT(slotSortAlbums(int))); ac->addAction(QLatin1String("album_sort"), d->albumSortAction); // Use same list order as in applicationsettings enum QStringList sortActionList; sortActionList.append(i18n("By Folder")); sortActionList.append(i18n("By Category")); sortActionList.append(i18n("By Date")); d->albumSortAction->setItems(sortActionList); // ----------------------------------------------------------- d->recurseAlbumsAction = new QAction(i18n("Include Album Sub-Tree"), this); d->recurseAlbumsAction->setCheckable(true); d->recurseAlbumsAction->setWhatsThis(i18n("Activate this option to show all sub-albums below " "the current album.")); connect(d->recurseAlbumsAction, SIGNAL(toggled(bool)), this, SLOT(slotRecurseAlbums(bool))); ac->addAction(QLatin1String("albums_recursive"), d->recurseAlbumsAction); d->recurseTagsAction = new QAction(i18n("Include Tag Sub-Tree"), this); d->recurseTagsAction->setCheckable(true); d->recurseTagsAction->setWhatsThis(i18n("Activate this option to show all images marked by the given tag " "and all its sub-tags.")); connect(d->recurseTagsAction, SIGNAL(toggled(bool)), this, SLOT(slotRecurseTags(bool))); ac->addAction(QLatin1String("tags_recursive"), d->recurseTagsAction); // ----------------------------------------------------------- d->imageSortAction = new KSelectAction(i18n("&Sort Items"), this); d->imageSortAction->setWhatsThis(i18n("The value by which the images in one album are sorted in the thumbnail view")); ac->addAction(QLatin1String("image_sort"), d->imageSortAction); // map to ItemSortSettings enum QAction* const sortByNameAction = d->imageSortAction->addAction(i18n("By Name")); QAction* const sortByPathAction = d->imageSortAction->addAction(i18n("By Path")); QAction* const sortByDateAction = d->imageSortAction->addAction(i18n("By Creation Date")); QAction* const sortByModDateAction = d->imageSortAction->addAction(i18n("By Modification Date")); QAction* const sortByFileSizeAction = d->imageSortAction->addAction(i18n("By File Size")); QAction* const sortByRatingAction = d->imageSortAction->addAction(i18n("By Rating")); QAction* const sortByImageSizeAction = d->imageSortAction->addAction(i18n("By Image Size")); QAction* const sortByAspectRatioAction = d->imageSortAction->addAction(i18n("By Aspect Ratio")); QAction* const sortBySimilarityAction = d->imageSortAction->addAction(i18n("By Similarity")); QAction* const sortByManualOrderAndNameAction = d->imageSortAction->addAction(i18n("By Manual and Name")); QAction* const sortByManualOrderAndDateAction = d->imageSortAction->addAction(i18n("By Manual and Date")); // activate the sort by similarity if the fuzzy search sidebar is active. Deactivate at start. sortBySimilarityAction->setEnabled(false); connect(d->view, SIGNAL(signalFuzzySidebarActive(bool)), sortBySimilarityAction, SLOT(setEnabled(bool))); connect(sortByNameAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByFileName); }); connect(sortByPathAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByFilePath); }); connect(sortByDateAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByCreationDate); }); connect(sortByModDateAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByModificationDate); }); connect(sortByFileSizeAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByFileSize); }); connect(sortByRatingAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByRating); }); connect(sortByImageSizeAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByImageSize); }); connect(sortByAspectRatioAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByAspectRatio); }); connect(sortBySimilarityAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortBySimilarity); }); connect(sortByManualOrderAndNameAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByManualOrderAndName); }); connect(sortByManualOrderAndDateAction, &QAction::triggered, this, [this]() { d->view->slotSortImages((int)ItemSortSettings::SortByManualOrderAndDate); }); // ----------------------------------------------------------- d->imageSortOrderAction = new KSelectAction(i18n("Item Sort &Order"), this); d->imageSortOrderAction->setWhatsThis(i18n("Defines whether images are sorted in ascending or descending manner.")); ac->addAction(QLatin1String("image_sort_order"), d->imageSortOrderAction); QAction* const sortAscendingAction = d->imageSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-ascending")), i18n("Ascending")); QAction* const sortDescendingAction = d->imageSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-descending")), i18n("Descending")); connect(sortAscendingAction, &QAction::triggered, this, [this]() { d->view->slotSortImagesOrder((int)ItemSortSettings::AscendingOrder); }); connect(sortDescendingAction, &QAction::triggered, this, [this]() { d->view->slotSortImagesOrder((int)ItemSortSettings::DescendingOrder); }); // ----------------------------------------------------------- d->imageSeparationAction = new KSelectAction(i18n("Separate Items"), this); d->imageSeparationAction->setWhatsThis(i18n("The categories in which the images in the thumbnail view are displayed")); ac->addAction(QLatin1String("image_separation"), d->imageSeparationAction); // map to ItemSortSettings enum QAction* const noCategoriesAction = d->imageSeparationAction->addAction(i18n("Flat List")); QAction* const separateByAlbumAction = d->imageSeparationAction->addAction(i18n("By Album")); QAction* const separateByFormatAction = d->imageSeparationAction->addAction(i18n("By Format")); QAction* const separateByMonthAction = d->imageSeparationAction->addAction(i18n("By Month")); connect(noCategoriesAction, &QAction::triggered, this, [this]() { d->view->slotSeparateImages((int)ItemSortSettings::OneCategory); }); connect(separateByAlbumAction, &QAction::triggered, this, [this]() { d->view->slotSeparateImages((int)ItemSortSettings::CategoryByAlbum); }); connect(separateByFormatAction, &QAction::triggered, this, [this]() { d->view->slotSeparateImages((int)ItemSortSettings::CategoryByFormat); }); connect(separateByMonthAction, &QAction::triggered, this, [this]() { d->view->slotSeparateImages((int)ItemSortSettings::CategoryByMonth); }); // ----------------------------------------------------------------- d->imageSeparationSortOrderAction = new KSelectAction(i18n("Item Separation Order"), this); d->imageSeparationSortOrderAction->setWhatsThis(i18n("The sort order of the groups of separated items")); ac->addAction(QLatin1String("image_separation_sort_order"), d->imageSeparationSortOrderAction); QAction* const sortSeparationsAscending = d->imageSeparationSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-ascending")), i18n("Ascending")); QAction* const sortSeparationsDescending = d->imageSeparationSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-descending")), i18n("Descending")); connect(sortSeparationsAscending, &QAction::triggered, this, [this]() { d->view->slotImageSeparationSortOrder((int)ItemSortSettings::AscendingOrder); }); connect(sortSeparationsDescending, &QAction::triggered, this, [this]() { d->view->slotImageSeparationSortOrder((int)ItemSortSettings::DescendingOrder); }); // ----------------------------------------------------------------- setupImageTransformActions(); setupExifOrientationActions(); // ----------------------------------------------------------------- d->selectAllAction = new QAction(i18n("Select All"), this); connect(d->selectAllAction, SIGNAL(triggered()), d->view, SLOT(slotSelectAll())); ac->addAction(QLatin1String("selectAll"), d->selectAllAction); ac->setDefaultShortcut(d->selectAllAction, Qt::CTRL + Qt::Key_A); // ----------------------------------------------------------------- d->selectNoneAction = new QAction(i18n("Select None"), this); connect(d->selectNoneAction, SIGNAL(triggered()), d->view, SLOT(slotSelectNone())); ac->addAction(QLatin1String("selectNone"), d->selectNoneAction); ac->setDefaultShortcut(d->selectNoneAction, Qt::CTRL + Qt::SHIFT + Qt::Key_A); // ----------------------------------------------------------------- d->selectInvertAction = new QAction(i18n("Invert Selection"), this); connect(d->selectInvertAction, SIGNAL(triggered()), d->view, SLOT(slotSelectInvert())); ac->addAction(QLatin1String("selectInvert"), d->selectInvertAction); ac->setDefaultShortcut(d->selectInvertAction, Qt::CTRL + Qt::Key_I); // ----------------------------------------------------------- d->showBarAction = new QAction(QIcon::fromTheme(QLatin1String("view-choose")), i18n("Show Thumbbar"), this); d->showBarAction->setCheckable(true); connect(d->showBarAction, SIGNAL(triggered()), this, SLOT(slotToggleShowBar())); ac->addAction(QLatin1String("showthumbs"), d->showBarAction); ac->setDefaultShortcut(d->showBarAction, Qt::CTRL + Qt::Key_T); // Provides a menu entry that allows showing/hiding the toolbar(s) setStandardToolBarMenuEnabled(true); // Provides a menu entry that allows showing/hiding the statusbar createStandardStatusBarAction(); // Standard 'Configure' menu actions createSettingsActions(); // ----------------------------------------------------------- d->zoomPlusAction = buildStdAction(StdZoomInAction, d->view, SLOT(slotZoomIn()), this); ac->addAction(QLatin1String("album_zoomin"), d->zoomPlusAction); // ----------------------------------------------------------- d->zoomMinusAction = buildStdAction(StdZoomOutAction, d->view, SLOT(slotZoomOut()), this); ac->addAction(QLatin1String("album_zoomout"), d->zoomMinusAction); // ----------------------------------------------------------- d->zoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Zoom to 100%"), this); connect(d->zoomTo100percents, SIGNAL(triggered()), d->view, SLOT(slotZoomTo100Percents())); ac->addAction(QLatin1String("album_zoomto100percents"), d->zoomTo100percents); ac->setDefaultShortcut(d->zoomTo100percents, Qt::CTRL + Qt::Key_Period); // ----------------------------------------------------------- d->zoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Fit to &Window"), this); connect(d->zoomFitToWindowAction, SIGNAL(triggered()), d->view, SLOT(slotFitToWindow())); ac->addAction(QLatin1String("album_zoomfit2window"), d->zoomFitToWindowAction); ac->setDefaultShortcut(d->zoomFitToWindowAction, Qt::CTRL + Qt::ALT + Qt::Key_E); // ----------------------------------------------------------- createFullScreenAction(QLatin1String("full_screen")); createSidebarActions(); // ----------------------------------------------------------- d->slideShowAction = new QMenu(i18n("Slideshow"), this); d->slideShowAction->setIcon(QIcon::fromTheme(QLatin1String("view-presentation"))); ac->addAction(QLatin1String("slideshow"), d->slideShowAction->menuAction()); d->slideShowAllAction = new QAction(i18n("All"), this); connect(d->slideShowAllAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowAll())); ac->addAction(QLatin1String("slideshow_all"), d->slideShowAllAction); ac->setDefaultShortcut(d->slideShowAllAction, Qt::Key_F9); d->slideShowAction->addAction(d->slideShowAllAction); d->slideShowSelectionAction = new QAction(i18n("Selection"), this); connect(d->slideShowSelectionAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowSelection())); ac->addAction(QLatin1String("slideshow_selected"), d->slideShowSelectionAction); ac->setDefaultShortcut(d->slideShowSelectionAction, Qt::ALT + Qt::Key_F9); d->slideShowAction->addAction(d->slideShowSelectionAction); d->slideShowRecursiveAction = new QAction(i18n("With All Sub-Albums"), this); connect(d->slideShowRecursiveAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowRecursive())); ac->addAction(QLatin1String("slideshow_recursive"), d->slideShowRecursiveAction); ac->setDefaultShortcut(d->slideShowRecursiveAction, Qt::SHIFT + Qt::Key_F9); d->slideShowAction->addAction(d->slideShowRecursiveAction); // ----------------------------------------------------------- d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")), i18n("Color-Managed View"), this); d->viewCMViewAction->setCheckable(true); connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView())); ac->addAction(QLatin1String("color_managed_view"), d->viewCMViewAction); ac->setDefaultShortcut(d->viewCMViewAction, Qt::Key_F12); // ----------------------------------------------------------- d->quitAction = buildStdAction(StdQuitAction, this, SLOT(slotExit()), this); ac->addAction(QLatin1String("app_exit"), d->quitAction); // ----------------------------------------------------------- createHelpActions(); // ----------------------------------------------------------- QAction* const findAction = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Search..."), this); connect(findAction, SIGNAL(triggered()), d->view, SLOT(slotNewKeywordSearch())); ac->addAction(QLatin1String("search_quick"), findAction); ac->setDefaultShortcut(findAction, Qt::CTRL + Qt::Key_F); // ----------------------------------------------------------- d->advSearchAction = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Advanced Search..."), this); connect(d->advSearchAction, SIGNAL(triggered()), d->view, SLOT(slotNewAdvancedSearch())); ac->addAction(QLatin1String("search_advanced"), d->advSearchAction); ac->setDefaultShortcut(d->advSearchAction, Qt::CTRL + Qt::ALT + Qt::Key_F); // ----------------------------------------------------------- QAction* const duplicatesAction = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")), i18n("Find Duplicates..."), this); connect(duplicatesAction, SIGNAL(triggered()), d->view, SLOT(slotNewDuplicatesSearch())); ac->addAction(QLatin1String("find_duplicates"), duplicatesAction); ac->setDefaultShortcut(duplicatesAction, Qt::CTRL + Qt::Key_D); // ----------------------------------------------------------- #ifdef HAVE_MYSQLSUPPORT QAction* const databaseMigrationAction = new QAction(QIcon::fromTheme(QLatin1String("network-server-database")), i18n("Database Migration..."), this); connect(databaseMigrationAction, SIGNAL(triggered()), this, SLOT(slotDatabaseMigration())); ac->addAction(QLatin1String("database_migration"), databaseMigrationAction); #endif // ----------------------------------------------------------- d->maintenanceAction = new QAction(QIcon::fromTheme(QLatin1String("run-build-prune")), i18n("Maintenance..."), this); connect(d->maintenanceAction, SIGNAL(triggered()), this, SLOT(slotMaintenance())); ac->addAction(QLatin1String("maintenance"), d->maintenanceAction); // ----------------------------------------------------------- QAction* const cameraAction = new QAction(i18n("Add Camera Manually..."), this); connect(cameraAction, SIGNAL(triggered()), this, SLOT(slotSetupCamera())); ac->addAction(QLatin1String("camera_add"), cameraAction); // ----------------------------------------------------------- // Load Cameras -- do this before the createGUI so that the cameras // are plugged into the toolbar at startup if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading cameras...")); } loadCameras(); // Load Themes populateThemes(); createGUI(xmlFile()); registerPluginsActions(); cleanupActions(); // NOTE: see bug #252130 and #283281 : we need to disable these actions when BQM is running. // These connections must be done after loading color theme else theme menu cannot be plugged to Settings menu, connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->bqmAction, SLOT(setDisabled(bool))); connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->imageAddCurrentQueueAction, SLOT(setDisabled(bool))); connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->imageAddNewQueueAction, SLOT(setDisabled(bool))); } void DigikamApp::setupAccelerators() { KActionCollection* const ac = actionCollection(); // Action are added by tag in ui.rc XML file QAction* const escapeAction = new QAction(i18n("Exit Preview Mode"), this); ac->addAction(QLatin1String("exit_preview_mode"), escapeAction); ac->setDefaultShortcut(escapeAction, Qt::Key_Escape); connect(escapeAction, SIGNAL(triggered()), this, SIGNAL(signalEscapePressed())); QAction* const nextImageAction = new QAction(i18n("Next Image"), this); nextImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-next"))); ac->addAction(QLatin1String("next_image"), nextImageAction); ac->setDefaultShortcut(nextImageAction, Qt::Key_Space); connect(nextImageAction, SIGNAL(triggered()), this, SIGNAL(signalNextItem())); QAction* const previousImageAction = new QAction(i18n("Previous Image"), this); previousImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-previous"))); ac->addAction(QLatin1String("previous_image"), previousImageAction); ac->setDefaultShortcuts(previousImageAction, QList() << Qt::Key_Backspace << Qt::SHIFT + Qt::Key_Space); connect(previousImageAction, SIGNAL(triggered()), this, SIGNAL(signalPrevItem())); QAction* const firstImageAction = new QAction(i18n("First Image"), this); ac->addAction(QLatin1String("first_image"), firstImageAction); ac->setDefaultShortcuts(firstImageAction, QList() << Qt::CTRL + Qt::Key_Home); connect(firstImageAction, SIGNAL(triggered()), this, SIGNAL(signalFirstItem())); QAction* const lastImageAction = new QAction(i18n("Last Image"), this); ac->addAction(QLatin1String("last_image"), lastImageAction); ac->setDefaultShortcuts(lastImageAction, QList() << Qt::CTRL + Qt::Key_End); connect(lastImageAction, SIGNAL(triggered()), this, SIGNAL(signalLastItem())); d->cutItemsAction = new QAction(i18n("Cu&t"), this); d->cutItemsAction->setIcon(QIcon::fromTheme(QLatin1String("edit-cut"))); d->cutItemsAction->setWhatsThis(i18n("Cut selection to clipboard")); ac->addAction(QLatin1String("cut_album_selection"), d->cutItemsAction); // NOTE: shift+del keyboard shortcut must not be assigned to Cut action // else the shortcut for Delete permanently collides with secondary shortcut of Cut ac->setDefaultShortcut(d->cutItemsAction, Qt::CTRL + Qt::Key_X); connect(d->cutItemsAction, SIGNAL(triggered()), this, SIGNAL(signalCutAlbumItemsSelection())); d->copyItemsAction = buildStdAction(StdCopyAction, this, SIGNAL(signalCopyAlbumItemsSelection()), this); ac->addAction(QLatin1String("copy_album_selection"), d->copyItemsAction); d->pasteItemsAction = buildStdAction(StdPasteAction, this, SIGNAL(signalPasteAlbumItemsSelection()), this); ac->addAction(QLatin1String("paste_album_selection"), d->pasteItemsAction); // Labels shortcuts must be registered here to be saved in XML GUI files if user customize it. d->tagsActionManager->registerLabelsActions(ac); QAction* const editTitles = new QAction(i18n("Edit Titles"), this); ac->addAction(QLatin1String("edit_titles"), editTitles); ac->setDefaultShortcut(editTitles, Qt::ALT + Qt::SHIFT + Qt::Key_T); connect(editTitles, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateTitles())); QAction* const editComments = new QAction(i18n("Edit Comments"), this); ac->addAction(QLatin1String("edit_comments"), editComments); ac->setDefaultShortcut(editComments, Qt::ALT + Qt::SHIFT + Qt::Key_C); connect(editComments, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateComments())); QAction* const assignedTags = new QAction(i18n("Show Assigned Tags"), this); ac->addAction(QLatin1String("assigned_tags"), assignedTags); ac->setDefaultShortcut(assignedTags, Qt::ALT + Qt::SHIFT + Qt::Key_A); connect(assignedTags, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateAssignedTags())); } void DigikamApp::setupExifOrientationActions() { KActionCollection* const ac = actionCollection(); d->imageExifOrientationActionMenu = new QMenu(i18n("Adjust Exif Orientation Tag"), this); ac->addAction(QLatin1String("image_set_exif_orientation"), d->imageExifOrientationActionMenu->menuAction()); d->imageSetExifOrientation1Action = new QAction(i18nc("normal exif orientation", "Normal"), this); d->imageSetExifOrientation1Action->setCheckable(true); d->imageSetExifOrientation2Action = new QAction(i18n("Flipped Horizontally"), this); d->imageSetExifOrientation2Action->setCheckable(true); d->imageSetExifOrientation3Action = new QAction(i18n("Rotated Upside Down"), this); d->imageSetExifOrientation3Action->setCheckable(true); d->imageSetExifOrientation4Action = new QAction(i18n("Flipped Vertically"), this); d->imageSetExifOrientation4Action->setCheckable(true); d->imageSetExifOrientation5Action = new QAction(i18n("Rotated Right / Horiz. Flipped"), this); d->imageSetExifOrientation5Action->setCheckable(true); d->imageSetExifOrientation6Action = new QAction(i18n("Rotated Right"), this); d->imageSetExifOrientation6Action->setCheckable(true); d->imageSetExifOrientation7Action = new QAction(i18n("Rotated Right / Vert. Flipped"), this); d->imageSetExifOrientation7Action->setCheckable(true); d->imageSetExifOrientation8Action = new QAction(i18n("Rotated Left"), this); d->imageSetExifOrientation8Action->setCheckable(true); d->exifOrientationActionGroup = new QActionGroup(d->imageExifOrientationActionMenu); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation1Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation2Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation3Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation4Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation5Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation6Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation7Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation8Action); d->imageSetExifOrientation1Action->setChecked(true); ac->addAction(QLatin1String("image_set_exif_orientation_normal"), d->imageSetExifOrientation1Action); ac->addAction(QLatin1String("image_set_exif_orientation_flipped_horizontal"), d->imageSetExifOrientation2Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_upside_down"), d->imageSetExifOrientation3Action); ac->addAction(QLatin1String("image_set_exif_orientation_flipped_vertically"), d->imageSetExifOrientation4Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right_hor_flipped"), d->imageSetExifOrientation5Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right"), d->imageSetExifOrientation6Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right_ver_flipped"), d->imageSetExifOrientation7Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_left"), d->imageSetExifOrientation8Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation1Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation2Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation3Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation4Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation5Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation6Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation7Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation8Action); connect(d->imageSetExifOrientation1Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(1); }); connect(d->imageSetExifOrientation2Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(2); }); connect(d->imageSetExifOrientation3Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(3); }); connect(d->imageSetExifOrientation4Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(4); }); connect(d->imageSetExifOrientation5Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(5); }); connect(d->imageSetExifOrientation6Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(6); }); connect(d->imageSetExifOrientation7Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(7); }); connect(d->imageSetExifOrientation8Action, &QAction::triggered, this, [this]() { d->view->slotImageExifOrientation(8); }); } void DigikamApp::setupImageTransformActions() { KActionCollection* const ac = actionCollection(); d->imageRotateActionMenu = new QMenu(i18n("Rotate"), this); d->imageRotateActionMenu->setIcon(QIcon::fromTheme(QLatin1String("object-rotate-right"))); QAction* const left = ac->addAction(QLatin1String("rotate_ccw")); left->setText(i18nc("rotate image left", "Left")); ac->setDefaultShortcut(left, Qt::CTRL + Qt::SHIFT + Qt::Key_Left); connect(left, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageRotateActionMenu->addAction(left); QAction* const right = ac->addAction(QLatin1String("rotate_cw")); right->setText(i18nc("rotate image right", "Right")); ac->setDefaultShortcut(right, Qt::CTRL + Qt::SHIFT + Qt::Key_Right); connect(right, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageRotateActionMenu->addAction(right); ac->addAction(QLatin1String("image_rotate"), d->imageRotateActionMenu->menuAction()); // ----------------------------------------------------------------------------------- d->imageFlipActionMenu = new QMenu(i18n("Flip"), this); d->imageFlipActionMenu->setIcon(QIcon::fromTheme(QLatin1String("flip-horizontal"))); QAction* const hori = ac->addAction(QLatin1String("flip_horizontal")); hori->setText(i18n("Horizontally")); ac->setDefaultShortcut(hori, Qt::CTRL + Qt::Key_Asterisk); connect(hori, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageFlipActionMenu->addAction(hori); QAction* const verti = ac->addAction(QLatin1String("flip_vertical")); verti->setText(i18n("Vertically")); ac->setDefaultShortcut(verti, Qt::CTRL + Qt::Key_Slash); connect(verti, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageFlipActionMenu->addAction(verti); ac->addAction(QLatin1String("image_flip"), d->imageFlipActionMenu->menuAction()); // ----------------------------------------------------------------------------------- d->imageAutoExifActionMenu = new QAction(i18n("Auto Rotate/Flip Using Exif Information"), this); connect(d->imageAutoExifActionMenu, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); ac->addAction(QLatin1String("image_transform_exif"), d->imageAutoExifActionMenu); } void DigikamApp::populateThemes() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading themes...")); } ThemeManager::instance()->setThemeMenuAction(new QMenu(i18n("&Themes"), this)); ThemeManager::instance()->registerThemeActions(this); ThemeManager::instance()->setCurrentTheme(ApplicationSettings::instance()->getCurrentTheme()); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); } void DigikamApp::preloadWindows() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading tools...")); } QueueMgrWindow::queueManagerWindow(); ImageWindow::imageWindow(); LightTableWindow::lightTableWindow(); d->tagsActionManager->registerTagsActionCollections(); } void DigikamApp::initGui() { // Initialize Actions --------------------------------------- d->deleteAction->setEnabled(false); d->renameAction->setEnabled(false); d->addImagesAction->setEnabled(false); d->propsEditAction->setEnabled(false); d->openInFileManagerAction->setEnabled(false); d->imageViewAction->setEnabled(false); d->imagePreviewAction->setEnabled(false); d->imageLightTableAction->setEnabled(false); d->imageAddLightTableAction->setEnabled(false); d->imageScanForFacesAction->setEnabled(false); d->imageFindSimilarAction->setEnabled(false); d->imageRenameAction->setEnabled(false); d->imageDeleteAction->setEnabled(false); d->imageExifOrientationActionMenu->setEnabled(false); d->openWithAction->setEnabled(false); d->slideShowSelectionAction->setEnabled(false); d->imageAutoExifActionMenu->setEnabled(false); foreach (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::GenericMetadata, this)) { ac->setEnabled(false); } d->albumSortAction->setCurrentItem((int)ApplicationSettings::instance()->getAlbumSortRole()); d->imageSortAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSortOrder()); d->imageSortOrderAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSorting()); d->imageSeparationAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSeparationMode()-1); // no action for enum 0 d->imageSeparationSortOrderAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSeparationSortOrder()); d->recurseAlbumsAction->setChecked(ApplicationSettings::instance()->getRecurseAlbums()); d->recurseTagsAction->setChecked(ApplicationSettings::instance()->getRecurseTags()); d->showBarAction->setChecked(ApplicationSettings::instance()->getShowThumbbar()); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 slotSwitchedToIconView(); } } // namespace Digikam diff --git a/core/app/main/digikamapp_solid.cpp b/core/app/main/digikamapp_solid.cpp index 5743214b0e..4320048683 100644 --- a/core/app/main/digikamapp_solid.cpp +++ b/core/app/main/digikamapp_solid.cpp @@ -1,706 +1,706 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2002-16-10 * Description : main digiKam interface implementation - Solid API based methods * * Copyright (C) 2002-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "digikamapp_p.h" // Solid includes #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wnonportable-include-path" #endif #include #include #include #include #include #include #include #include #include #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif namespace Digikam { // NOTE: static methods to not expose whole digiKam to Solid API. bool s_checkSolidCamera(const Solid::Device& cameraDevice) { const Solid::Camera* const camera = cameraDevice.as(); if (!camera) { qCDebug(DIGIKAM_GENERAL_LOG) << "Solid device" << cameraDevice.description() << "is not a camera"; return false; } QStringList drivers = camera->supportedDrivers(); qCDebug(DIGIKAM_GENERAL_LOG) << "checkSolidCamera: Found Camera " << QString::fromUtf8("%1 %2").arg(cameraDevice.vendor()).arg(cameraDevice.product()) << " protocols " << camera->supportedProtocols() << " drivers " << camera->supportedDrivers(QLatin1String("ptp")); // We handle gphoto2 cameras in this loop if (!(camera->supportedDrivers().contains(QLatin1String("gphoto")) || camera->supportedProtocols().contains(QLatin1String("ptp")))) { return false; } QVariant driverHandle = camera->driverHandle(QLatin1String("gphoto")); if (!driverHandle.canConvert(QVariant::List)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } QList driverHandleList = driverHandle.toList(); if ((driverHandleList.size() < 3) || (driverHandleList.at(0).toString() != QLatin1String("usb")) || !driverHandleList.at(1).canConvert(QVariant::Int) || !driverHandleList.at(2).canConvert(QVariant::Int)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } return true; } QString s_labelForSolidCamera(const Solid::Device& cameraDevice) { QString vendor = cameraDevice.vendor(); QString product = cameraDevice.product(); if (product == QLatin1String("USB Imaging Interface") || product == QLatin1String("USB Vendor Specific Interface")) { Solid::Device parentUsbDevice = cameraDevice.parent(); if (parentUsbDevice.isValid()) { vendor = parentUsbDevice.vendor(); product = parentUsbDevice.product(); if (!vendor.isEmpty() && !product.isEmpty()) { if (vendor == QLatin1String("Canon, Inc.")) { vendor = QLatin1String("Canon"); if (product.startsWith(QLatin1String("Canon "))) { product = product.mid(6); // cut off another "Canon " from product } if (product.endsWith(QLatin1String(" (ptp)"))) { product.chop(6); // cut off " (ptp)" } } else if (vendor == QLatin1String("Fuji Photo Film Co., Ltd")) { vendor = QLatin1String("Fuji"); } else if (vendor == QLatin1String("Nikon Corp.")) { vendor = QLatin1String("Nikon"); if (product.startsWith(QLatin1String("NIKON "))) { product = product.mid(6); } } } } } return vendor + QLatin1Char(' ') + product; } // -------------------------------------------------------------------------------------------------- void DigikamApp::fillSolidMenus() { QHash newAppearanceTimes; d->usbMediaMenu->clear(); d->cardReaderMenu->clear(); // delete the actionGroups to avoid duplicate menu entries delete d->solidUsmActionGroup; delete d->solidCameraActionGroup; d->solidCameraActionGroup = new QActionGroup(this); connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidCamera(QAction*))); d->solidUsmActionGroup = new QActionGroup(this); connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidUsmDevice(QAction*))); // -------------------------------------------------------- QList cameraDevices = Solid::Device::listFromType(Solid::DeviceInterface::Camera); - foreach(const Solid::Device& cameraDevice, cameraDevices) + foreach (const Solid::Device& cameraDevice, cameraDevices) { // USM camera: will be handled below if (cameraDevice.is()) { continue; } if (!s_checkSolidCamera(cameraDevice)) { continue; } // -------------------------------------------------------- QString l = s_labelForSolidCamera(cameraDevice); QString label = CameraNameHelper::cameraNameAutoDetected(l.trimmed()); // -------------------------------------------------------- QString iconName = cameraDevice.icon(); if (iconName.isEmpty()) { iconName = QLatin1String("camera-photo"); } QAction* const action = new QAction(label, d->solidCameraActionGroup); action->setIcon(QIcon::fromTheme(iconName)); // set data to identify device in action slot slotSolidSetupDevice action->setData(cameraDevice.udi()); newAppearanceTimes[cameraDevice.udi()] = d->cameraAppearanceTimes.contains(cameraDevice.udi()) ? d->cameraAppearanceTimes.value(cameraDevice.udi()) : QDateTime::currentDateTime(); d->cameraMenu->addAction(action); } QList storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); - foreach(const Solid::Device& accessDevice, storageDevices) + foreach (const Solid::Device& accessDevice, storageDevices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice ; currentDevice.isValid() ; currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } const Solid::StorageDrive* const drive = driveDevice.as(); QString driveType; bool isHarddisk = false; switch (drive->driveType()) { // skip these case Solid::StorageDrive::CdromDrive: case Solid::StorageDrive::Floppy: case Solid::StorageDrive::Tape: default: continue; // accept card readers case Solid::StorageDrive::CompactFlash: driveType = i18n("CompactFlash Card Reader"); break; case Solid::StorageDrive::MemoryStick: driveType = i18n("Memory Stick Reader"); break; case Solid::StorageDrive::SmartMedia: driveType = i18n("SmartMedia Card Reader"); break; case Solid::StorageDrive::SdMmc: driveType = i18n("SD / MMC Card Reader"); break; case Solid::StorageDrive::Xd: driveType = i18n("xD Card Reader"); break; case Solid::StorageDrive::HardDisk: // We don't want to list HardDisk partitions, but USB Mass Storage devices. // Don't know what is the exact difference between removable and hotpluggable. if (drive->isRemovable() || drive->isHotpluggable()) { isHarddisk = true; if (drive->bus() == Solid::StorageDrive::Usb) { driveType = i18n("USB Disk"); } else { driveType = i18nc("non-USB removable storage device", "Disk"); } break; } else { continue; } } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice ; currentDevice.isValid() ; currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } bool isCamera = accessDevice.is(); const Solid::StorageAccess* const access = accessDevice.as(); const Solid::StorageVolume* const volume = volumeDevice.as(); if (volume->isIgnored()) { continue; } QString label; if (isCamera) { label = accessDevice.vendor() + QLatin1Char(' ') + accessDevice.product(); } else { QString labelOrProduct; if (!volume->label().isEmpty()) { labelOrProduct = volume->label(); } else if (!volumeDevice.product().isEmpty()) { labelOrProduct = volumeDevice.product(); } else if (!volumeDevice.vendor().isEmpty()) { labelOrProduct = volumeDevice.vendor(); } else if (!driveDevice.product().isEmpty()) { labelOrProduct = driveDevice.product(); } if (!labelOrProduct.isNull()) { if (!access->filePath().isEmpty()) { label += i18nc(" \"\" at ", "%1 \"%2\" at %3", driveType, labelOrProduct, QDir::toNativeSeparators(access->filePath())); } else { label += i18nc(" \"\"", "%1 \"%2\"", driveType, labelOrProduct); } } else { if (!access->filePath().isEmpty()) { label += i18nc(" at ", "%1 at %2", driveType, QDir::toNativeSeparators(access->filePath())); } else { label += driveType; } } if (volume->size()) { label += i18nc("device label etc... ()", " (%1)", ItemPropertiesTab::humanReadableBytesCount(volume->size())); } } QString iconName; if (!driveDevice.icon().isEmpty()) { iconName = driveDevice.icon(); } else if (!accessDevice.icon().isEmpty()) { iconName = accessDevice.icon(); } else if (!volumeDevice.icon().isEmpty()) { iconName = volumeDevice.icon(); } QAction* const action = new QAction(label, d->solidUsmActionGroup); if (!iconName.isEmpty()) { action->setIcon(QIcon::fromTheme(iconName)); } // set data to identify device in action slot slotSolidSetupDevice action->setData(accessDevice.udi()); newAppearanceTimes[accessDevice.udi()] = d->cameraAppearanceTimes.contains(accessDevice.udi()) ? d->cameraAppearanceTimes.value(accessDevice.udi()) : QDateTime::currentDateTime(); if (isCamera) { d->cameraMenu->addAction(action); } if (isHarddisk) { d->usbMediaMenu->addAction(action); } else { d->cardReaderMenu->addAction(action); } } /* //TODO: Find best usable solution when no devices are connected: One entry, hide, or disable? // Add one entry telling that no device is available if (d->cameraSolidMenu->isEmpty()) { QAction* const action = d->cameraSolidMenu->addAction(i18n("No Camera Connected")); action->setEnabled(false); } if (d->usbMediaMenu->isEmpty()) { QAction* const action = d->usbMediaMenu->addAction(i18n("No Storage Devices Found")); action->setEnabled(false); } if (d->cardReaderMenu->isEmpty()) { QAction* const action = d->cardReaderMenu->addAction(i18n("No Card Readers Available")); action->setEnabled(false); } // hide empty menus d->cameraSolidMenu->menuAction()->setVisible(!d->cameraSolidMenu->isEmpty()); d->usbMediaMenu->menuAction()->setVisible(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->menuAction()->setVisible(!d->cardReaderMenu->isEmpty()); */ d->cameraAppearanceTimes = newAppearanceTimes; // disable empty menus d->usbMediaMenu->setEnabled(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->setEnabled(!d->cardReaderMenu->isEmpty()); updateCameraMenu(); updateQuickImportAction(); } void DigikamApp::connectToSolidNotifiers() { connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), this, SLOT(slotSolidDeviceChanged(QString)), Qt::QueuedConnection); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), this, SLOT(slotSolidDeviceChanged(QString)), Qt::QueuedConnection); // -- queued connections ------------------------------------------- connect(this, SIGNAL(queuedOpenCameraUiFromPath(QString)), this, SLOT(slotOpenCameraUiFromPath(QString)), Qt::QueuedConnection); connect(this, SIGNAL(queuedOpenSolidDevice(QString)), this, SLOT(slotOpenSolidDevice(QString)), Qt::QueuedConnection); } void DigikamApp::openSolidCamera(const QString& udi, const QString& cameraLabel) { // if there is already an open ImportUI for the device, show and raise it, and be done if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); if (device.isValid()) { if (cameraLabel.isNull()) { QString label = s_labelForSolidCamera(device); } Solid::Camera* const camera = device.as(); QList list = camera->driverHandle(QLatin1String("gphoto")).toList(); // all sanity checks have already been done when creating the action if (list.size() < 3) { return; } // NOTE: See bug #262296: With KDE 4.6, Solid API return device vendor id // and product id in hexadecimal strings. bool ok; int vendorId = list.at(1).toString().toInt(&ok, 16); int productId = list.at(2).toString().toInt(&ok, 16); QString model, port; if (CameraList::findConnectedCamera(vendorId, productId, model, port)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Found camera from ids " << vendorId << " " << productId << " camera is: " << model << " at " << port; // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(cameraLabel, model, port, QLatin1String("/"), 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to detect camera with GPhoto2 from Solid information"; } } } void DigikamApp::openSolidUsmDevice(const QString& udi, const QString& givenLabel) { QString mediaLabel = givenLabel; // if there is already an open ImportUI for the device, show and raise it if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); if (device.isValid()) { Solid::StorageAccess* const access = device.as(); if (!access) { return; } if (!access->isAccessible()) { QApplication::setOverrideCursor(Qt::WaitCursor); if (!access->setup()) { return; } d->eventLoop = new QEventLoop(this); // NOTE: Lambda function to not expose whole digiKam to Solid API. connect(access, &Solid::StorageAccess::setupDone, [=](Solid::ErrorType errorType, QVariant errorData, const QString& /*udi*/) { if (!d->eventLoop) { return; } if (errorType == Solid::NoError) { d->eventLoop->exit(0); } else { d->solidErrorMessage = i18n("Cannot access the storage device.\n"); d->solidErrorMessage += errorData.toString(); d->eventLoop->exit(1); } } ); int returnCode = d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents); delete d->eventLoop; d->eventLoop = nullptr; QApplication::restoreOverrideCursor(); if (returnCode == 1) { QMessageBox::critical(this, qApp->applicationName(), d->solidErrorMessage); return; } } // Create Camera UI QString path = QDir::fromNativeSeparators(access->filePath()); if (mediaLabel.isNull()) { mediaLabel = path; } // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(i18n("Images on %1", mediaLabel), QLatin1String("directory browse"), QLatin1String("Fixed"), path, 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } } void DigikamApp::slotOpenSolidCamera(QAction* action) { QString udi = action->data().toString(); openSolidCamera(udi, action->iconText()); } void DigikamApp::slotOpenSolidUsmDevice(QAction* action) { QString udi = action->data().toString(); openSolidUsmDevice(udi, action->iconText()); } void DigikamApp::slotOpenSolidDevice(const QString& udi) { // Identifies device as either Camera or StorageAccess and calls methods accordingly Solid::Device device(udi); if (!device.isValid()) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified device (\"%1\") is not valid.", udi)); return; } if (device.is()) { openSolidUsmDevice(udi); } else if (device.is()) { if (!s_checkSolidCamera(device)) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified camera (\"%1\") is not supported.", udi)); return; } openSolidCamera(udi); } } void DigikamApp::slotSolidDeviceChanged(const QString& udi) { qCDebug(DIGIKAM_GENERAL_LOG) << "slotSolidDeviceChanged:" << udi; fillSolidMenus(); } } // namespace Digikam diff --git a/core/app/views/tableview/tableview.cpp b/core/app/views/tableview/tableview.cpp index 4c02a0b6ef..b40eb6445c 100644 --- a/core/app/views/tableview/tableview.cpp +++ b/core/app/views/tableview/tableview.cpp @@ -1,661 +1,661 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-02-11 * Description : Table view * * Copyright (C) 2013 by Michael G. Hansen * Copyright (C) 2017 by Simon Frei * * 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, 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. * * ============================================================ */ #include "tableview.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "advancedrenamedialog.h" #include "advancedrenameprocessdialog.h" #include "applicationsettings.h" #include "contextmenuhelper.h" #include "digikam_debug.h" #include "fileactionmngr.h" #include "album.h" #include "itemviewutilities.h" #include "tableview_columnfactory.h" #include "tableview_model.h" #include "tableview_selection_model_syncer.h" #include "tableview_shared.h" #include "tableview_treeview.h" namespace Digikam { class ItemAlbumModel; class ItemFilterModel; class Q_DECL_HIDDEN TableView::Private { public: explicit Private() : columnProfiles(), thumbnailSize(), imageViewUtilities(nullptr) { } QList columnProfiles; ThumbnailSize thumbnailSize; ItemViewUtilities* imageViewUtilities; }; TableView::TableView(QItemSelectionModel* const selectionModel, DCategorizedSortFilterProxyModel* const imageFilterModel, QWidget* const parent) : QWidget(parent), StateSavingObject(this), d(new Private()), s(new TableViewShared()) { s->isActive = false; s->tableView = this; s->thumbnailLoadThread = new ThumbnailLoadThread(this); s->imageFilterModel = dynamic_cast(imageFilterModel); s->imageModel = dynamic_cast(imageFilterModel->sourceModel()); s->imageFilterSelectionModel = selectionModel; s->columnFactory = new TableViewColumnFactory(s.data(), this); QVBoxLayout* const vbox1 = new QVBoxLayout(); s->tableViewModel = new TableViewModel(s.data(), this); s->tableViewSelectionModel = new QItemSelectionModel(s->tableViewModel); s->tableViewSelectionModelSyncer = new TableViewSelectionModelSyncer(s.data(), this); s->treeView = new TableViewTreeView(s.data(), this); s->treeView->installEventFilter(this); d->imageViewUtilities = new ItemViewUtilities(this); connect(s->treeView, SIGNAL(activated(QModelIndex)), this, SLOT(slotItemActivated(QModelIndex))); connect(s->treeView, SIGNAL(signalZoomInStep()), this, SIGNAL(signalZoomInStep())); connect(s->treeView, SIGNAL(signalZoomOutStep()), this, SIGNAL(signalZoomOutStep())); connect(s->tableViewSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SIGNAL(signalItemsChanged())); connect(s->treeView, SIGNAL(collapsed(QModelIndex)), this, SIGNAL(signalItemsChanged())); connect(s->treeView, SIGNAL(expanded(QModelIndex)), this, SIGNAL(signalItemsChanged())); connect(s->tableViewModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(signalItemsChanged())); connect(s->tableViewModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(signalItemsChanged())); connect(s->tableViewModel, SIGNAL(layoutChanged()), this, SIGNAL(signalItemsChanged())); connect(s->tableViewModel, SIGNAL(modelReset()), this, SIGNAL(signalItemsChanged())); vbox1->addWidget(s->treeView); vbox1->setContentsMargins(QMargins()); setLayout(vbox1); } TableView::~TableView() { } void TableView::doLoadState() { const KConfigGroup group = getConfigGroup(); TableViewColumnProfile profile; const KConfigGroup groupCurrentProfile = group.group("Current Profile"); profile.loadSettings(groupCurrentProfile); s->tableViewModel->loadColumnProfile(profile); const TableViewModel::GroupingMode groupingMode = TableViewModel::GroupingMode(group.readEntry("Grouping mode", int(TableViewModel::GroupingShowSubItems))); s->tableViewModel->setGroupingMode(groupingMode); if (!profile.headerState.isEmpty()) { s->treeView->header()->restoreState(profile.headerState); } } void TableView::doSaveState() { KConfigGroup group = getConfigGroup(); TableViewColumnProfile profile = s->tableViewModel->getColumnProfile(); profile.headerState = s->treeView->header()->saveState(); KConfigGroup groupCurrentProfile = group.group("Current Profile"); profile.saveSettings(groupCurrentProfile); group.writeEntry("Grouping mode", int(s->tableViewModel->groupingMode())); } void TableView::slotItemActivated(const QModelIndex& tableViewIndex) { const ItemInfo info = s->tableViewModel->imageInfo(tableViewIndex); if (info.isNull()) { return; } if (qApp->queryKeyboardModifiers() != Qt::MetaModifier) { if (ApplicationSettings::instance()->getItemLeftClickAction() == ApplicationSettings::ShowPreview) { emit signalPreviewRequested(info); } else { d->imageViewUtilities->openInfos(info, allItemInfos(), currentAlbum()); } } else { d->imageViewUtilities->openInfosWithDefaultApplication(QList() << info); } } bool TableView::eventFilter(QObject* watched, QEvent* event) { // we are looking for context menu events for the table view if ((watched == s->treeView) && (event->type() == QEvent::ContextMenu)) { QContextMenuEvent* const e = static_cast(event); e->accept(); const QModelIndex contextMenuIndex = s->treeView->indexAt(e->pos()); if (contextMenuIndex.isValid()) { emit signalShowContextMenuOnInfo( e, s->tableViewModel->imageInfo(contextMenuIndex), getExtraGroupingActions()); } else { emit signalShowContextMenu(e, getExtraGroupingActions()); } // event has been filtered by us return true; } return QObject::eventFilter(watched, event); } void TableView::setThumbnailSize(const ThumbnailSize& size) { d->thumbnailSize = size; const QList columnObjects = s->tableViewModel->getColumnObjects(); - foreach(TableViewColumn* const iColumn, columnObjects) + foreach (TableViewColumn* const iColumn, columnObjects) { iColumn->updateThumbnailSize(); } } ThumbnailSize TableView::getThumbnailSize() const { return d->thumbnailSize; } Album* TableView::currentAlbum() const { ItemAlbumModel* const albumModel = qobject_cast(s->imageModel); if (!albumModel) { return nullptr; } if (albumModel->currentAlbums().isEmpty()) { return nullptr; } return albumModel->currentAlbums().first(); } void TableView::slotPaste() { DragDropViewImplementation* const dragDropViewImplementation = s->treeView; dragDropViewImplementation->paste(); } ItemInfo TableView::currentInfo() const { return s->tableViewModel->imageInfo(s->tableViewSelectionModel->currentIndex()); } ItemInfoList TableView::allItemInfos(bool grouping) const { if (grouping) { return s->treeView->resolveGrouping(ItemInfoList(s->tableViewModel->allItemInfo())); } return ItemInfoList(s->tableViewModel->allItemInfo()); } bool TableView::allNeedGroupResolving(const ApplicationSettings::OperationType type) const { return s->treeView->needGroupResolving(type, allItemInfos()); } bool TableView::selectedNeedGroupResolving(const ApplicationSettings::OperationType type) const { return s->treeView->needGroupResolving(type, selectedItemInfos()); } void TableView::slotDeleteSelected(const ItemViewUtilities::DeleteMode deleteMode) { const ItemInfoList infoList = selectedItemInfos(true); /// @todo Update parameter naming for deleteImages if (d->imageViewUtilities->deleteImages(infoList, deleteMode)) { slotAwayFromSelection(); } } void TableView::slotDeleteSelectedWithoutConfirmation(const ItemViewUtilities::DeleteMode deleteMode) { const ItemInfoList infoList = selectedItemInfos(true); d->imageViewUtilities->deleteImagesDirectly(infoList, deleteMode); slotAwayFromSelection(); } QList TableView::getExtraGroupingActions() { QList actionList; const TableViewModel::GroupingMode currentGroupingMode = s->tableViewModel->groupingMode(); QAction* const actionHideGrouped = new QAction(i18n("Hide grouped items"), this); actionHideGrouped->setCheckable(true); actionHideGrouped->setChecked(currentGroupingMode == TableViewModel::GroupingHideGrouped); actionHideGrouped->setData(QVariant::fromValue(TableViewModel::GroupingHideGrouped)); connect(actionHideGrouped, SIGNAL(triggered(bool)), this, SLOT(slotGroupingModeActionTriggered())); actionList << actionHideGrouped; QAction* const actionIgnoreGrouping = new QAction(i18n("Ignore grouping"), this); actionIgnoreGrouping->setCheckable(true); actionIgnoreGrouping->setChecked(currentGroupingMode == TableViewModel::GroupingIgnoreGrouping); actionIgnoreGrouping->setData(QVariant::fromValue(TableViewModel::GroupingIgnoreGrouping)); connect(actionIgnoreGrouping, SIGNAL(triggered(bool)), this, SLOT(slotGroupingModeActionTriggered())); actionList << actionIgnoreGrouping; QAction* const actionShowSubItems = new QAction(i18n("Show grouping in tree"), this); actionShowSubItems->setCheckable(true); actionShowSubItems->setChecked(currentGroupingMode == TableViewModel::GroupingShowSubItems); actionShowSubItems->setData(QVariant::fromValue(TableViewModel::GroupingShowSubItems)); connect(actionShowSubItems, SIGNAL(triggered(bool)), this, SLOT(slotGroupingModeActionTriggered())); actionList << actionShowSubItems; return actionList; } void TableView::slotGroupingModeActionTriggered() { const QAction* const senderAction = qobject_cast(sender()); if (!senderAction) { return; } const TableViewModel::GroupingMode newGroupingMode = senderAction->data().value(); s->tableViewModel->setGroupingMode(newGroupingMode); } int TableView::numberOfSelectedItems() const { return s->tableViewSelectionModel->selectedRows().count(); } void TableView::slotGoToRow(const int rowNumber, const bool relativeMove) { int nextDeepRowNumber = rowNumber; if (relativeMove) { const QModelIndex currentTableViewIndex = s->tableViewSelectionModel->currentIndex(); const int currentDeepRowNumber = s->tableViewModel->indexToDeepRowNumber(currentTableViewIndex); nextDeepRowNumber += currentDeepRowNumber; } const QModelIndex nextIndex = s->tableViewModel->deepRowIndex(nextDeepRowNumber); if (nextIndex.isValid()) { const QItemSelection rowSelection = s->tableViewSelectionModelSyncer->targetIndexToRowItemSelection(nextIndex); s->tableViewSelectionModel->select(rowSelection, QItemSelectionModel::ClearAndSelect); s->tableViewSelectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::Select); } } ItemInfo TableView::deepRowItemInfo(const int rowNumber, const bool relative) const { int targetRowNumber = rowNumber; if (relative) { const QModelIndex& currentTableViewIndex = s->tableViewSelectionModel->currentIndex(); if (!currentTableViewIndex.isValid()) { return ItemInfo(); } const int currentDeepRowNumber = s->tableViewModel->indexToDeepRowNumber(currentTableViewIndex); targetRowNumber += currentDeepRowNumber; } const QModelIndex targetIndex = s->tableViewModel->deepRowIndex(targetRowNumber); return s->tableViewModel->imageInfo(targetIndex); } ItemInfo TableView::nextInfo() const { const QModelIndex cIndex = s->tableViewSelectionModel->currentIndex(); const int currentDeepRowNumber = s->tableViewModel->indexToDeepRowNumber(cIndex); const int nextDeepRowNumber = currentDeepRowNumber + 1; if (nextDeepRowNumber>=s->tableViewModel->deepRowCount()) { return ItemInfo(); } const QModelIndex nextDeepRowIndex = s->tableViewModel->deepRowIndex(nextDeepRowNumber); return s->tableViewModel->imageInfo(nextDeepRowIndex); } ItemInfo TableView::previousInfo() const { const QModelIndex cIndex = s->tableViewSelectionModel->currentIndex(); const int currentDeepRowNumber = s->tableViewModel->indexToDeepRowNumber(cIndex); const int previousDeepRowNumber = currentDeepRowNumber - 1; if (previousDeepRowNumber < 0) { return ItemInfo(); } const QModelIndex previousDeepRowIndex = s->tableViewModel->deepRowIndex(previousDeepRowNumber); return s->tableViewModel->imageInfo(previousDeepRowIndex); } void TableView::slotSetCurrentWhenAvailable(const qlonglong id) { const QModelIndex idx = s->tableViewModel->indexFromImageId(id, 0); if (!idx.isValid()) { /// @todo Actually buffer this request until the model is fully populated return; } s->tableViewSelectionModel->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect); } /** * @brief Unselects the current selection and changes the current item * * @todo This may not work correctly if grouped items are deleted, but are not selected */ void TableView::slotAwayFromSelection() { QModelIndexList selection = s->tableViewSelectionModel->selectedRows(0); if (selection.isEmpty()) { return; } const QModelIndex firstIndex = s->tableViewModel->deepRowIndex(0); const QModelIndex lastIndex = s->tableViewModel->deepRowIndex(-1); if (selection.contains(firstIndex) && selection.contains(lastIndex)) { // both the first and the last index are selected, we have to // select an index inbetween const int nextFreeDeepRow = s->tableViewModel->firstDeepRowNotInList(selection); if (nextFreeDeepRow < 0) { s->tableViewSelectionModel->clearSelection(); s->tableViewSelectionModel->setCurrentIndex(QModelIndex(), QItemSelectionModel::ClearAndSelect); } else { const QModelIndex nextFreeIndex = s->tableViewModel->deepRowIndex(nextFreeDeepRow); s->tableViewSelectionModel->setCurrentIndex(nextFreeIndex, QItemSelectionModel::ClearAndSelect); const QItemSelection nextFreeIndexAsRow = s->tableViewSelectionModelSyncer->targetIndexToRowItemSelection(nextFreeIndex); s->tableViewSelectionModel->select(nextFreeIndexAsRow, QItemSelectionModel::ClearAndSelect); } } else if (selection.contains(lastIndex)) { const int firstSelectedRowNumber = s->tableViewModel->indexToDeepRowNumber(selection.first()); const QModelIndex newIndex = s->tableViewModel->deepRowIndex(firstSelectedRowNumber-1); s->tableViewSelectionModel->setCurrentIndex(newIndex, QItemSelectionModel::ClearAndSelect); const QItemSelection newIndexAsRow = s->tableViewSelectionModelSyncer->targetIndexToRowItemSelection(newIndex); s->tableViewSelectionModel->select(newIndexAsRow, QItemSelectionModel::ClearAndSelect); } else { const int lastSelectedRowNumber = s->tableViewModel->indexToDeepRowNumber(selection.last()); const QModelIndex newIndex = s->tableViewModel->deepRowIndex(lastSelectedRowNumber+1); s->tableViewSelectionModel->setCurrentIndex(newIndex, QItemSelectionModel::ClearAndSelect); const QItemSelection newIndexAsRow = s->tableViewSelectionModelSyncer->targetIndexToRowItemSelection(newIndex); s->tableViewSelectionModel->select(newIndexAsRow, QItemSelectionModel::ClearAndSelect); } } void TableView::clearSelection() { s->tableViewSelectionModel->clearSelection(); } void TableView::invertSelection() { const int deepRowCount = s->tableViewModel->deepRowCount(); QList rowsToSelect; int lastSelectedRow = -1; /// @todo Create a DeepRowIterator because there is a lot of overhead here for (int i = 0; i < deepRowCount; ++i) { const QModelIndex iIndex = s->tableViewModel->deepRowIndex(i); if (s->tableViewSelectionModel->isSelected(iIndex)) { if (i - 1 > lastSelectedRow) { for (int j = lastSelectedRow + 1; j < i; ++j) { rowsToSelect << j; } } lastSelectedRow = i; } } if (lastSelectedRow + 1 < deepRowCount) { for (int j = lastSelectedRow + 1; j < deepRowCount; ++j) { rowsToSelect << j; } } s->tableViewSelectionModel->clearSelection(); foreach (const int i, rowsToSelect) { const QModelIndex iIndex = s->tableViewModel->deepRowIndex(i); const QItemSelection is = s->tableViewSelectionModelSyncer->targetIndexToRowItemSelection(iIndex); s->tableViewSelectionModel->select(is, QItemSelectionModel::Select); } } void TableView::selectAll() { /// @todo This only selects expanded items. s->treeView->selectAll(); } void TableView::slotSetActive(const bool isActive) { if (s->isActive != isActive) { s->isActive = isActive; s->tableViewModel->slotSetActive(isActive); s->tableViewSelectionModelSyncer->slotSetActive(isActive); } } ItemInfoList TableView::selectedItemInfos(bool grouping) const { ItemInfoList infos = ItemInfoList(s->tableViewModel->imageInfos(s->tableViewSelectionModel->selectedRows())); if (grouping) { return s->treeView->resolveGrouping(infos); } return infos; } ItemInfoList TableView::selectedItemInfosCurrentFirst(bool grouping) const { QModelIndexList indexes = s->tableViewSelectionModel->selectedRows(); const QModelIndex current = s->tableViewModel->toCol0(s->tableViewSelectionModel->currentIndex()); if (!indexes.isEmpty()) { if (indexes.first() != current) { if (indexes.removeOne(current)) { indexes.prepend(current); } } } if (grouping) { return s->treeView->resolveGrouping(ItemInfoList(s->tableViewModel->imageInfos(indexes))); } return ItemInfoList(s->tableViewModel->imageInfos(indexes)); } void TableView::rename() { ItemInfoList infos = selectedItemInfos(); if (s->treeView->needGroupResolving(ApplicationSettings::Rename, infos)) { infos = s->treeView->resolveGrouping(infos); } QList urls = infos.toImageUrlList(); bool loop = false; NewNamesList newNamesList; do { qCDebug(DIGIKAM_GENERAL_LOG) << "Selected URLs to rename: " << urls; QPointer dlg = new AdvancedRenameDialog(this); dlg->slotAddImages(urls); if (dlg->exec() != QDialog::Accepted) { delete dlg; break; } if (!loop) { slotAwayFromSelection(); loop = true; } newNamesList = dlg->newNames(); delete dlg; setFocus(); qApp->processEvents(); if (!newNamesList.isEmpty()) { QPointer dlg2 = new AdvancedRenameProcessDialog(newNamesList, this); dlg2->exec(); s->tableViewModel->scheduleResort(); urls = dlg2->failedUrls(); delete dlg2; } } while (!urls.isEmpty() && !newNamesList.isEmpty()); } } // namespace Digikam diff --git a/core/app/views/tableview/tableview_selection_model_syncer.cpp b/core/app/views/tableview/tableview_selection_model_syncer.cpp index 191aaf91f3..2ae9153489 100644 --- a/core/app/views/tableview/tableview_selection_model_syncer.cpp +++ b/core/app/views/tableview/tableview_selection_model_syncer.cpp @@ -1,360 +1,360 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2013-02-18 * Description : Sync QItemSelectionModel of ItemFilterModel and TableViewModel * * Copyright (C) 2013 by Michael G. Hansen * * 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, 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. * * ============================================================ */ #include "tableview_selection_model_syncer.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "itemfiltermodel.h" #include "tableview_model.h" #include "tableview_shared.h" namespace Digikam { class Q_DECL_HIDDEN TableViewSelectionModelSyncer::Private { public: explicit Private() : syncing(false) { } bool syncing; }; TableViewSelectionModelSyncer::TableViewSelectionModelSyncer(TableViewShared* const sharedObject, QObject* const parent) : QObject(parent), d(new Private()), s(sharedObject) { connect(s->imageFilterSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(slotSourceCurrentChanged(QModelIndex,QModelIndex))); connect(s->imageFilterSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSourceSelectionChanged(QItemSelection,QItemSelection))); connect(s->tableViewSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(slotTargetCurrentChanged(QModelIndex,QModelIndex))); connect(s->tableViewSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotTargetSelectionChanged(QItemSelection,QItemSelection))); connect(s->tableViewModel, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(slotTargetColumnsInserted(QModelIndex,int,int))); connect(s->tableViewModel, SIGNAL(modelReset()), this, SLOT(slotTargetModelReset())); connect(s->tableViewModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotTargetModelRowsInserted(QModelIndex,int,int))); /// @todo This is necessary to re-sync the selection when tags are added to images. /// Check whether both are necessary or whether we need more. connect(s->imageFilterModel, SIGNAL(layoutChanged()), this, SLOT(slotSourceModelReset())); connect(s->imageFilterModel, SIGNAL(modelReset()), this, SLOT(slotSourceModelReset())); slotDoInitialSync(); } TableViewSelectionModelSyncer::~TableViewSelectionModelSyncer() { } QModelIndex TableViewSelectionModelSyncer::toSource(const QModelIndex& tableViewIndex) const { return s->tableViewModel->toItemFilterModelIndex(tableViewIndex); } QModelIndex TableViewSelectionModelSyncer::toTarget(const QModelIndex& sourceIndex) const { return s->tableViewModel->fromItemFilterModelIndex(sourceIndex); } int TableViewSelectionModelSyncer::targetModelColumnCount() const { return s->tableViewModel->columnCount(QModelIndex()); } QItemSelection TableViewSelectionModelSyncer::targetIndexToRowItemSelection(const QModelIndex& targetIndex) const { const int row = targetIndex.row(); const QModelIndex topLeft = s->tableViewModel->index(row, 0, targetIndex.parent()); const QModelIndex bottomRight = s->tableViewModel->index(row, targetModelColumnCount()-1, targetIndex.parent()); const QItemSelection mySelection(topLeft, bottomRight); return mySelection; } void TableViewSelectionModelSyncer::slotDoInitialSync() { if (!s->isActive) { return; } d->syncing = true; s->tableViewSelectionModel->clearSelection(); const QItemSelection sourceSelection = s->imageFilterSelectionModel->selection(); const QItemSelection targetSelection = itemSelectionToTarget(sourceSelection); s->tableViewSelectionModel->select(targetSelection, QItemSelectionModel::Select); const QModelIndex targetIndexCurrent = toTarget(s->imageFilterSelectionModel->currentIndex()); s->tableViewSelectionModel->setCurrentIndex(targetIndexCurrent, QItemSelectionModel::NoUpdate); d->syncing = false; } void TableViewSelectionModelSyncer::slotSourceCurrentChanged(const QModelIndex& current, const QModelIndex& previous) { if (!s->isActive) { return; } Q_UNUSED(previous) if (d->syncing) { return; } d->syncing = true; // we have to select the whole row of the target index const QModelIndex targetIndexCurrent = toTarget(current); s->tableViewSelectionModel->setCurrentIndex(targetIndexCurrent, QItemSelectionModel::Select); d->syncing = false; } QItemSelection TableViewSelectionModelSyncer::itemSelectionToSource(const QItemSelection& selection) const { QItemSelection sourceSelection; - foreach(const QItemSelectionRange& range, selection) + foreach (const QItemSelectionRange& range, selection) { const int firstRow = range.top(); const int lastRow = range.bottom(); for (int row = firstRow; row<=lastRow; ++row) { const QModelIndex tableViewIndex = s->tableViewModel->index(row, 0, range.parent()); const QModelIndex sourceIndex = s->tableViewModel->toItemFilterModelIndex(tableViewIndex); if (sourceIndex.isValid()) { sourceSelection.select(sourceIndex, sourceIndex); } } } return sourceSelection; } QItemSelection TableViewSelectionModelSyncer::itemSelectionToTarget(const QItemSelection& selection) const { const int targetColumnCount = targetModelColumnCount(); QItemSelection targetSelection; - foreach(const QItemSelectionRange& range, selection) + foreach (const QItemSelectionRange& range, selection) { const int firstRow = range.top(); const int lastRow = range.bottom(); for (int row = firstRow; row<=lastRow; ++row) { const QModelIndex sourceIndex = s->imageFilterModel->index(row, 0, range.parent()); const QModelIndex tableViewIndexTopLeft = s->tableViewModel->fromItemFilterModelIndex(sourceIndex); const QModelIndex tableViewIndexBottomRight = s->tableViewModel->index( tableViewIndexTopLeft.row(), targetColumnCount-1, tableViewIndexTopLeft.parent() ); targetSelection.select(tableViewIndexTopLeft, tableViewIndexBottomRight); } } return targetSelection; } void TableViewSelectionModelSyncer::slotSourceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (!s->isActive) { return; } if (d->syncing) { return; } d->syncing = true; const QItemSelection targetSelection = itemSelectionToTarget(selected); s->tableViewSelectionModel->select(targetSelection, QItemSelectionModel::Select); const QItemSelection targetDeselection = itemSelectionToTarget(deselected); s->tableViewSelectionModel->select(targetDeselection, QItemSelectionModel::Deselect); d->syncing = false; } void TableViewSelectionModelSyncer::slotTargetCurrentChanged(const QModelIndex& current, const QModelIndex& previous) { if (!s->isActive) { return; } Q_UNUSED(previous) if (d->syncing) { return; } d->syncing = true; const QModelIndex sourceIndexCurrent = toSource(current); s->imageFilterSelectionModel->setCurrentIndex(sourceIndexCurrent, QItemSelectionModel::Select); d->syncing = false; } void TableViewSelectionModelSyncer::slotTargetSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (!s->isActive) { return; } if (d->syncing) { return; } d->syncing = true; const QItemSelection sourceSelection = itemSelectionToSource(selected); s->imageFilterSelectionModel->select(sourceSelection, QItemSelectionModel::Select); const QItemSelection sourceDeselection = itemSelectionToSource(deselected); s->imageFilterSelectionModel->select(sourceDeselection, QItemSelectionModel::Deselect); d->syncing = false; } void TableViewSelectionModelSyncer::slotSourceModelReset() { // QAbstractItemModel will also react to the modelReset signal // make sure we transfer the update after that. QTimer::singleShot(0, this, SLOT(slotDoInitialSync())); } void TableViewSelectionModelSyncer::slotTargetColumnsInserted(const QModelIndex& parent, int start, int end) { if (!s->isActive) { return; } Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) if (d->syncing) { return; } // New columns were inserted. We have to make sure that all selected rows include the new columns. // We just re-perform the initial synchronization. /// @todo There may be more efficient ways. slotDoInitialSync(); } void Digikam::TableViewSelectionModelSyncer::slotSetActive(const bool isActive) { if (isActive) { slotSourceModelReset(); } } void Digikam::TableViewSelectionModelSyncer::slotTargetModelReset() { slotDoInitialSync(); } void Digikam::TableViewSelectionModelSyncer::slotTargetModelRowsInserted(const QModelIndex& parent, int start, int end) { if (!s->isActive) { return; } // look up the state of the source indexes and transfer them here for (int i = start; i <= end; ++i) { const QModelIndex iTarget = s->tableViewModel->index(i, 0, parent); if (!iTarget.isValid()) { continue; } const QModelIndex iSource = toSource(iTarget); if (!iSource.isValid()) { continue; } if (s->imageFilterSelectionModel->isSelected(iSource)) { const QItemSelection targetSelection = targetIndexToRowItemSelection(iTarget); s->tableViewSelectionModel->select(targetSelection, QItemSelectionModel::Select); } } // also transfer the current index if necessary const QModelIndex sourceCurrentIndex = s->imageFilterSelectionModel->currentIndex(); const QModelIndex targetIndexCurrent = toTarget(sourceCurrentIndex); s->tableViewSelectionModel->setCurrentIndex(targetIndexCurrent, QItemSelectionModel::NoUpdate); } } // namespace Digikam diff --git a/core/dplugins/editor/enhance/hotpixels/hotpixelfixer.cpp b/core/dplugins/editor/enhance/hotpixels/hotpixelfixer.cpp index 64320e2b46..9836f5e314 100644 --- a/core/dplugins/editor/enhance/hotpixels/hotpixelfixer.cpp +++ b/core/dplugins/editor/enhance/hotpixels/hotpixelfixer.cpp @@ -1,398 +1,398 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-03-27 * Description : Threaded image filter to fix hot pixels * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2005-2006 by Unai Garro * * 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, 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. * * ============================================================ */ #include "hotpixelfixer.h" // C++ includes #include #include // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "dimg.h" #ifdef HAVE_FLOAT_H # if HAVE_FLOAT_H # include # endif #endif #ifndef DBL_MIN # define DBL_MIN 1e-37 #endif #ifndef DBL_MAX # define DBL_MAX 1e37 #endif namespace DigikamEditorHotPixelsToolPlugin { HotPixelFixer::HotPixelFixer(QObject* const parent) : DImgThreadedFilter(parent) { m_interpolationMethod = TWODIM_DIRECTION; initFilter(); } HotPixelFixer::HotPixelFixer(DImg* const orgImage, QObject* const parent, const QList& hpList, int interpolationMethod) : DImgThreadedFilter(orgImage, parent, QLatin1String("HotPixels")) { m_hpList = hpList; m_interpolationMethod = interpolationMethod; initFilter(); } HotPixelFixer::~HotPixelFixer() { cancelFilter(); } QString HotPixelFixer::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Hot Pixels Tool")); } Digikam::FilterAction HotPixelFixer::filterAction() { DefaultFilterAction action; action.addParameter(QLatin1String("interpolationMethod"), m_interpolationMethod); - foreach(const HotPixel& hp, m_hpList) + foreach (const HotPixel& hp, m_hpList) { QString hpString = QString::fromUtf8("%1-%2x%3-%4x%5").arg(hp.luminosity) .arg(hp.rect.x()).arg(hp.rect.y()) .arg(hp.rect.width()).arg(hp.rect.height()); action.addParameter(QLatin1String("hotPixel"), hpString); } return std::move(action); } void HotPixelFixer::readParameters(const FilterAction& action) { m_interpolationMethod = action.parameter(QLatin1String("interpolationMethod")).toInt(); QRegExp exp(QLatin1String("(\\d+)-(\\d+)x(\\d+)-(\\d+)x(\\d+)")); - foreach(const QVariant& var, action.parameters().values(QLatin1String("hotPixel"))) + foreach (const QVariant& var, action.parameters().values(QLatin1String("hotPixel"))) { if (exp.exactMatch(var.toString())) { HotPixel hp; hp.luminosity = exp.cap(1).toInt(); hp.rect = QRect(exp.cap(2).toInt(), exp.cap(3).toInt(), exp.cap(4).toInt(), exp.cap(5).toInt()); m_hpList << hp; } } } void HotPixelFixer::filterImage() { QList ::ConstIterator it; QList ::ConstIterator end(m_hpList.constEnd()); for (it = m_hpList.constBegin() ; it != end ; ++it) { HotPixel hp = *it; interpolate(m_orgImage, hp, m_interpolationMethod); } m_destImage = m_orgImage; } // Interpolates a pixel block void HotPixelFixer::interpolate(DImg& img, HotPixel& hp, int method) { const int xPos = hp.x(); const int yPos = hp.y(); bool sixtBits = img.sixteenBit(); // Interpolate pixel. switch (method) { case AVERAGE_INTERPOLATION: { // We implement the bidimendional one first. // TODO: implement the rest of directions (V & H) here //case twodim: // { int sum_weight = 0; double vr = 0.0, vg = 0.0, vb = 0.0; int x, y; DColor col; for (x = xPos ; x < xPos+hp.width() ; ++x) { if (validPoint(img,QPoint(x, yPos - 1))) { col = img.getPixelColor(x, yPos - 1); vr += col.red(); vg += col.green(); vb += col.blue(); ++sum_weight; } if (validPoint(img,QPoint(x, yPos + hp.height()))) { col = img.getPixelColor(x, yPos + hp.height()); vr += col.red(); vg += col.green(); vb += col.blue(); ++sum_weight; } } for (y = yPos ; y < hp.height() ; ++y) { if (validPoint(img, QPoint(xPos - 1, y))) { col = img.getPixelColor(xPos, y); vr += col.red(); vg += col.green(); vb += col.blue(); ++sum_weight; } if (validPoint(img, QPoint(xPos + hp.width(), y))) { col = img.getPixelColor(xPos + hp.width(), y); vr += col.red(); vg += col.green(); vb += col.blue(); ++sum_weight; } } if (sum_weight > 0) { vr /= (double)sum_weight; vg /= (double)sum_weight; vb /= (double)sum_weight; for (x = 0 ; x < hp.width() ; ++x) { for (y = 0 ; y < hp.height() ; ++y) { if (validPoint(img, QPoint(xPos + x, yPos + y))) { int alpha = sixtBits ? 65535 : 255; int ir = (int)round(vr); int ig = (int)round(vg); int ib = (int)round(vb); img.setPixelColor(xPos + x, yPos + y, DColor(ir, ig, ib, alpha, sixtBits)); } } } } break; } case LINEAR_INTERPOLATION: weightPixels(img, hp, LINEAR_INTERPOLATION, TWODIM_DIRECTION, sixtBits ? 65535: 255); break; case QUADRATIC_INTERPOLATION: weightPixels(img, hp, QUADRATIC_INTERPOLATION, TWODIM_DIRECTION, sixtBits ? 65535 : 255); break; case CUBIC_INTERPOLATION: weightPixels(img, hp, CUBIC_INTERPOLATION, TWODIM_DIRECTION, sixtBits ? 65535 : 255); break; } } void HotPixelFixer::weightPixels(DImg& img, HotPixel& px, int method, Direction dir, int maxComponent) { //TODO: implement direction here too for (int iComp = 0 ; iComp < 3 ; ++iComp) { // Obtain weight data block. HotPixelsWeights w; int polynomeOrder = -1; switch (method) { case AVERAGE_INTERPOLATION: // Gilles: to prevent warnings from compiler. break; case LINEAR_INTERPOLATION: polynomeOrder=1; break; case QUADRATIC_INTERPOLATION: polynomeOrder=2; break; case CUBIC_INTERPOLATION: polynomeOrder=3; break; } if (polynomeOrder<0) { return; } // In the one-dimensional case, the width must be 1, // and the size must be stored in height w.setWidth(dir == TWODIM_DIRECTION ? px.width() : 1); w.setHeight(dir == HORIZONTAL_DIRECTION ? px.width() : px.height()); w.setPolynomeOrder(polynomeOrder); w.setTwoDim(dir == TWODIM_DIRECTION); //TODO: check this, it must not recalculate existing calculated weights //for now I don't think it is finding the duplicates fine, so it uses //the previous one always... //if (mWeightList.find(w)==mWeightList.end()) //{ w.calculateHotPixelsWeights(); // mWeightList.append(w); //} // Calculate weighted pixel sum. for (int y = 0 ; y= DBL_MIN) { component = (int) (v / sum_weight); //Clamp value if (component < 0) { component = 0; } if (component > maxComponent) { component = maxComponent; } } else if (v >= 0.0) { component = maxComponent; } else { component = 0; } if (iComp == 0) { color.setRed(component); } else if (iComp == 1) { color.setGreen(component); } else { color.setBlue(component); } img.setPixelColor(px.x() + x, px.y() + y,color); } } } } } } // namespace DigikamEditorHotPixelsToolPlugin diff --git a/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp b/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp index a879fd5958..1923043d63 100644 --- a/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp +++ b/core/dplugins/generic/metadata/geolocationedit/dialog/geolocationedit.cpp @@ -1,1091 +1,1091 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-05-16 * Description : A tool to edit geolocation * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2010-2014 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * Copyright (C) 2014 by Justus Schwartz * * 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, 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. * * ============================================================ */ #include "geolocationedit.h" // Qt includes #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 // KDE includes #include #include #include // Local includes #include "dlayoutbox.h" #include "digikam_config.h" #include "itemmarkertiler.h" #include "trackmanager.h" #include "gpscommon.h" #include "gpsitemmodel.h" #include "mapdragdrophandler.h" #include "gpsitemlist.h" #include "gpsitemlistdragdrophandler.h" #include "gpsitemlistcontextmenu.h" #include "gpscorrelatorwidget.h" #include "digikam_debug.h" #include "dmessagebox.h" #include "rgwidget.h" #include "kmlwidget.h" #include "statusprogressbar.h" #include "searchwidget.h" #include "backend-rg.h" #include "gpsitemdetails.h" #include "gpsgeoifacemodelhelper.h" #include "dxmlguiwindow.h" #include "gpsbookmarkowner.h" #include "gpsbookmarkmodelhelper.h" #ifdef GPSSYNC_MODELTEST # include #endif using namespace Digikam; namespace DigikamGenericGeolocationEditPlugin { struct SaveChangedImagesHelper { public: explicit SaveChangedImagesHelper(GPSItemModel* const model) : imageModel(model) { } QPair operator()(const QPersistentModelIndex& itemIndex) { GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex); if (!item) return QPair(QUrl(), QString()); return QPair(item->url(), item->saveChanges()); } public: typedef QPair result_type; GPSItemModel* const imageModel; }; // --------------------------------------------------------------------------------- struct LoadFileMetadataHelper { public: explicit LoadFileMetadataHelper(GPSItemModel* const model) : imageModel(model) { } QPair operator()(const QPersistentModelIndex& itemIndex) { GPSItemContainer* const item = imageModel->itemFromIndex(itemIndex); if (!item) return QPair(QUrl(), QString()); item->loadImageData(); return QPair(item->url(), QString()); } public: typedef QPair result_type; GPSItemModel* const imageModel; }; // --------------------------------------------------------------------------------- class Q_DECL_HIDDEN GeolocationEdit::Private { public: explicit Private() { imageModel = nullptr; selectionModel = nullptr; uiEnabled = true; listViewContextMenu = nullptr; trackManager = nullptr; fileIOFutureWatcher = nullptr; fileIOCountDone = 0; fileIOCountTotal = 0; fileIOCloseAfterSaving = false; buttonBox = nullptr; VSplitter = nullptr; HSplitter = nullptr; treeView = nullptr; stackedWidget = nullptr; tabBar = nullptr; splitterSize = 0; undoStack = nullptr; undoView = nullptr; progressBar = nullptr; progressCancelButton = nullptr; progressCancelObject = nullptr; detailsWidget = nullptr; correlatorWidget = nullptr; rgWidget = nullptr; searchWidget = nullptr; kmlWidget = nullptr; mapSplitter = nullptr; mapWidget = nullptr; mapWidget2 = nullptr; mapDragDropHandler = nullptr; mapModelHelper = nullptr; geoifaceMarkerModel = nullptr; sortActionOldestFirst = nullptr; sortActionYoungestFirst = nullptr; sortMenu = nullptr; mapLayout = MapLayoutOne; cbMapLayout = nullptr; bookmarkOwner = nullptr; actionBookmarkVisibility = nullptr; iface = nullptr; } // General things GPSItemModel* imageModel; QItemSelectionModel* selectionModel; bool uiEnabled; GPSItemListContextMenu* listViewContextMenu; TrackManager* trackManager; // Loading and saving QFuture > fileIOFuture; QFutureWatcher >* fileIOFutureWatcher; int fileIOCountDone; int fileIOCountTotal; bool fileIOCloseAfterSaving; // UI QDialogButtonBox* buttonBox; QSplitter* VSplitter; QSplitter* HSplitter; GPSItemList* treeView; QStackedWidget* stackedWidget; QTabBar* tabBar; int splitterSize; QUndoStack* undoStack; QUndoView* undoView; // UI: progress StatusProgressBar* progressBar; QPushButton* progressCancelButton; QObject* progressCancelObject; QString progressCancelSlot; // UI: tab widgets GPSItemDetails* detailsWidget; GPSCorrelatorWidget* correlatorWidget; RGWidget* rgWidget; SearchWidget* searchWidget; KmlWidget* kmlWidget; // map: UI MapLayout mapLayout; QSplitter* mapSplitter; MapWidget* mapWidget; MapWidget* mapWidget2; // map: helpers MapDragDropHandler* mapDragDropHandler; GPSGeoIfaceModelHelper* mapModelHelper; ItemMarkerTiler* geoifaceMarkerModel; // map: actions QAction* sortActionOldestFirst; QAction* sortActionYoungestFirst; QMenu* sortMenu; QComboBox* cbMapLayout; GPSBookmarkOwner* bookmarkOwner; QAction* actionBookmarkVisibility; DInfoInterface* iface; }; GeolocationEdit::GeolocationEdit(QWidget* const parent, DInfoInterface* const iface) : DPluginDialog(parent, QLatin1String("Geolocation Edit Settings")), d(new Private) { setAttribute(Qt::WA_DeleteOnClose, true); setWindowTitle(i18n("Geolocation Editor")); setMinimumSize(300, 400); setModal(true); d->iface = iface; d->imageModel = new GPSItemModel(this); d->selectionModel = new QItemSelectionModel(d->imageModel); d->trackManager = new TrackManager(this); #ifdef GPSSYNC_MODELTEST new ModelTest(d->imageModel, this); #endif d->bookmarkOwner = new GPSBookmarkOwner(d->imageModel, this); d->undoStack = new QUndoStack(this); d->stackedWidget = new QStackedWidget(); d->searchWidget = new SearchWidget(d->bookmarkOwner, d->imageModel, d->selectionModel, d->stackedWidget); GPSItemContainer::setHeaderData(d->imageModel); d->mapModelHelper = new GPSGeoIfaceModelHelper(d->imageModel, d->selectionModel, this); d->mapModelHelper->addUngroupedModelHelper(d->bookmarkOwner->bookmarkModelHelper()); d->mapModelHelper->addUngroupedModelHelper(d->searchWidget->getModelHelper()); d->mapDragDropHandler = new MapDragDropHandler(d->imageModel, d->mapModelHelper); d->geoifaceMarkerModel = new ItemMarkerTiler(d->mapModelHelper, this); d->actionBookmarkVisibility = new QAction(this); d->actionBookmarkVisibility->setIcon(QIcon::fromTheme(QLatin1String("bookmark-new"))); d->actionBookmarkVisibility->setToolTip(i18n("Display bookmarked positions on the map.")); d->actionBookmarkVisibility->setCheckable(true); connect(d->actionBookmarkVisibility, SIGNAL(changed()), this, SLOT(slotBookmarkVisibilityToggled())); QVBoxLayout* const mainLayout = new QVBoxLayout(this); setLayout(mainLayout); DHBox* const hboxMain = new DHBox(this); mainLayout->addWidget(hboxMain, 10); d->HSplitter = new QSplitter(Qt::Horizontal, hboxMain); d->HSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // ------------------------------------------------------------------------------------------------ DHBox* const hbox = new DHBox(this); QLabel* const labelMapLayout = new QLabel(i18n("Layout:"), hbox); d->cbMapLayout = new QComboBox(hbox); d->cbMapLayout->addItem(i18n("One map"), QVariant::fromValue(MapLayoutOne)); d->cbMapLayout->addItem(i18n("Two maps - horizontal"), QVariant::fromValue(MapLayoutHorizontal)); d->cbMapLayout->addItem(i18n("Two maps - vertical"), QVariant::fromValue(MapLayoutVertical)); labelMapLayout->setBuddy(d->cbMapLayout); d->progressBar = new StatusProgressBar(hbox); d->progressBar->setVisible(false); d->progressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode); d->progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->progressCancelButton = new QPushButton(hbox); d->progressCancelButton->setVisible(false); d->progressCancelButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->progressCancelButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))); connect(d->progressCancelButton, SIGNAL(clicked()), this, SLOT(slotProgressCancelButtonClicked())); m_buttons->addButton(QDialogButtonBox::Apply); m_buttons->addButton(QDialogButtonBox::Close); m_buttons->setParent(hbox); connect(m_buttons->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &GeolocationEdit::slotApplyClicked); connect(m_buttons->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &GeolocationEdit::close); mainLayout->addWidget(hbox, 0); // ------------------------------------------------------------------------------------------------ d->VSplitter = new QSplitter(Qt::Vertical, d->HSplitter); d->HSplitter->addWidget(d->VSplitter); d->HSplitter->setStretchFactor(0, 10); d->sortMenu = new QMenu(this); d->sortMenu->setTitle(i18n("Sorting")); QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu); sortOrderExclusive->setExclusive(true); connect(sortOrderExclusive, SIGNAL(triggered(QAction*)), this, SLOT(slotSortOptionTriggered(QAction*))); d->sortActionOldestFirst = new QAction(i18n("Show oldest first"), sortOrderExclusive); d->sortActionOldestFirst->setCheckable(true); d->sortMenu->addAction(d->sortActionOldestFirst); d->sortActionYoungestFirst = new QAction(i18n("Show youngest first"), sortOrderExclusive); d->sortMenu->addAction(d->sortActionYoungestFirst); d->sortActionYoungestFirst->setCheckable(true); QWidget* mapVBox = nullptr; d->mapWidget = makeMapWidget(&mapVBox); d->searchWidget->setPrimaryMapWidget(d->mapWidget); d->mapSplitter = new QSplitter(this); d->mapSplitter->addWidget(mapVBox); d->VSplitter->addWidget(d->mapSplitter); d->treeView = new GPSItemList(this); d->treeView->setModelAndSelectionModel(d->imageModel, d->selectionModel); d->treeView->setDragDropHandler(new GPSItemListDragDropHandler(this)); d->treeView->setDragEnabled(true); // TODO: save and restore the state of the header // TODO: add a context menu to the header to select which columns should be visible // TODO: add sorting by column d->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); d->treeView->setSortingEnabled(true); d->VSplitter->addWidget(d->treeView); d->listViewContextMenu = new GPSItemListContextMenu(d->treeView, d->bookmarkOwner); d->HSplitter->addWidget(d->stackedWidget); d->HSplitter->setCollapsible(1, true); d->splitterSize = 0; DVBox* const vboxTabBar = new DVBox(hboxMain); vboxTabBar->layout()->setContentsMargins(QMargins()); vboxTabBar->layout()->setSpacing(0); d->tabBar = new QTabBar(vboxTabBar); d->tabBar->setShape(QTabBar::RoundedEast); dynamic_cast(vboxTabBar->layout())->addStretch(200); d->tabBar->addTab(i18n("Details")); d->tabBar->addTab(i18n("GPS Correlator")); d->tabBar->addTab(i18n("Undo/Redo")); d->tabBar->addTab(i18n("Reverse Geocoding")); d->tabBar->addTab(i18n("Search")); d->tabBar->addTab(i18n("KML Export")); d->tabBar->installEventFilter(this); d->detailsWidget = new GPSItemDetails(d->stackedWidget, d->imageModel); d->stackedWidget->addWidget(d->detailsWidget); d->correlatorWidget = new GPSCorrelatorWidget(d->stackedWidget, d->imageModel, d->trackManager); d->stackedWidget->addWidget(d->correlatorWidget); d->undoView = new QUndoView(d->undoStack, d->stackedWidget); d->stackedWidget->addWidget(d->undoView); d->rgWidget = new RGWidget(d->imageModel, d->selectionModel, d->iface->tagFilterModel(), d->stackedWidget); d->stackedWidget->addWidget(d->rgWidget); d->stackedWidget->addWidget(d->searchWidget); d->kmlWidget = new KmlWidget(this, d->imageModel, d->iface); d->stackedWidget->addWidget(d->kmlWidget); // --------------------------------------------------------------- connect(d->treeView, SIGNAL(signalImageActivated(QModelIndex)), this, SLOT(slotImageActivated(QModelIndex))); connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->correlatorWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->correlatorWidget, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->correlatorWidget, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->correlatorWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->mapModelHelper, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->rgWidget, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->rgWidget, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->rgWidget, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->rgWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->searchWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool)), this, SLOT(slotSetUIEnabled(bool))); connect(d->listViewContextMenu, SIGNAL(signalSetUIEnabled(bool,QObject*const,QString)), this, SLOT(slotSetUIEnabled(bool,QObject*const,QString))); connect(d->listViewContextMenu, SIGNAL(signalProgressSetup(int,QString)), this, SLOT(slotProgressSetup(int,QString))); connect(d->listViewContextMenu, SIGNAL(signalProgressChanged(int)), this, SLOT(slotProgressChanged(int))); connect(d->listViewContextMenu, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->tabBar, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentTabChanged(int))); connect(d->bookmarkOwner->bookmarkModelHelper(), SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->detailsWidget, SIGNAL(signalUndoCommand(GPSUndoCommand*)), this, SLOT(slotGPSUndoCommand(GPSUndoCommand*))); connect(d->cbMapLayout, SIGNAL(activated(int)), this, SLOT(slotLayoutChanged(int))); connect(this, SIGNAL(signalMetadataChangedForUrl(QUrl)), d->iface, SLOT(slotMetadataChangedForUrl(QUrl))); readSettings(); d->mapWidget->setActive(true); setItems(d->iface->currentGPSItems()); } GeolocationEdit::~GeolocationEdit() { delete d; } bool GeolocationEdit::eventFilter(QObject* const o, QEvent* const e) { if ((o == d->tabBar) && (e->type() == QEvent::MouseButtonPress)) { QMouseEvent const* m = static_cast(e); QPoint p (m->x(), m->y()); const int var = d->tabBar->tabAt(p); if (var < 0) { return false; } QList sizes = d->HSplitter->sizes(); if (d->splitterSize == 0) { if (sizes.at(1) == 0) { sizes[1] = d->stackedWidget->widget(var)->minimumSizeHint().width(); } else if (d->tabBar->currentIndex() == var) { d->splitterSize = sizes.at(1); sizes[1] = 0; } } else { sizes[1] = d->splitterSize; d->splitterSize = 0; } d->tabBar->setCurrentIndex(var); d->stackedWidget->setCurrentIndex(var); d->HSplitter->setSizes(sizes); d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) && (d->splitterSize == 0)); return true; } return QWidget::eventFilter(o, e); } void GeolocationEdit::slotCurrentTabChanged(int index) { d->tabBar->setCurrentIndex(index); d->stackedWidget->setCurrentIndex(index); d->detailsWidget->slotSetActive(d->stackedWidget->currentWidget() == d->detailsWidget); } void GeolocationEdit::setCurrentTab(int index) { d->tabBar->setCurrentIndex(index); d->stackedWidget->setCurrentIndex(index); QList sizes = d->HSplitter->sizes(); if (d->splitterSize >= 0) { sizes[1] = d->splitterSize; d->splitterSize = 0; } d->HSplitter->setSizes(sizes); d->detailsWidget->slotSetActive((d->stackedWidget->currentWidget() == d->detailsWidget) && (d->splitterSize == 0)); } void GeolocationEdit::setImages(const QList& images) { QList items; - foreach(const QUrl& u, images) + foreach (const QUrl& u, images) { items << new GPSItemContainer(u); } setItems(items); } void GeolocationEdit::setItems(const QList& items) { foreach (GPSItemContainer* const newItem, items) { newItem->loadImageData(); d->imageModel->addItem(newItem); } QList imagesToLoad; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { imagesToLoad << d->imageModel->index(i, 0); } slotSetUIEnabled(false); slotProgressSetup(imagesToLoad.count(), i18n("Loading metadata -")); // initiate the saving d->fileIOCountDone = 0; d->fileIOCountTotal = imagesToLoad.count(); d->fileIOFutureWatcher = new QFutureWatcher >(this); connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(slotFileMetadataLoaded(int,int))); d->fileIOFuture = QtConcurrent::mapped(imagesToLoad, LoadFileMetadataHelper(d->imageModel)); d->fileIOFutureWatcher->setFuture(d->fileIOFuture); } void GeolocationEdit::slotFileMetadataLoaded(int beginIndex, int endIndex) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex; d->fileIOCountDone += (endIndex-beginIndex); slotProgressChanged(d->fileIOCountDone); if (d->fileIOCountDone == d->fileIOCountTotal) { slotSetUIEnabled(true); } } void GeolocationEdit::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); // -------------------------- // TODO: sanely determine a default backend const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget->readSettingsFromGroup(&groupMapWidget); const KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget"); d->correlatorWidget->readSettingsFromGroup(&groupCorrelatorWidget); const KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View"); d->treeView->readSettingsFromGroup(&groupTreeView); const KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget"); d->searchWidget->readSettingsFromGroup(&groupSearchWidget); const KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget"); d->rgWidget->readSettingsFromGroup(&groupRGWidget); const KConfigGroup groupDialog = KConfigGroup(&group, "Dialog"); // -------------------------- setCurrentTab(group.readEntry("Current Tab", 0)); const bool showOldestFirst = group.readEntry("Show oldest images first", false); if (showOldestFirst) { d->sortActionOldestFirst->setChecked(true); d->mapWidget->setSortKey(1); } else { d->sortActionYoungestFirst->setChecked(true); d->mapWidget->setSortKey(0); } d->actionBookmarkVisibility->setChecked(group.readEntry("Bookmarks visible", false)); slotBookmarkVisibilityToggled(); if (group.hasKey("SplitterState V1")) { const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState V1", QByteArray())); if (!splitterState.isEmpty()) { d->VSplitter->restoreState(splitterState); } } if (group.hasKey("SplitterState H1")) { const QByteArray splitterState = QByteArray::fromBase64(group.readEntry("SplitterState H1", QByteArray())); if (!splitterState.isEmpty()) { d->HSplitter->restoreState(splitterState); } } d->splitterSize = group.readEntry("Splitter H1 CollapsedSize", 0); // ---------------------------------- d->mapLayout = MapLayout(group.readEntry("Map Layout", QVariant::fromValue(int(MapLayoutOne))).value()); d->cbMapLayout->setCurrentIndex(d->mapLayout); adjustMapLayout(false); if (d->mapWidget2) { const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget 2"); d->mapWidget2->readSettingsFromGroup(&groupMapWidget); d->mapWidget2->setActive(true); } } void GeolocationEdit::saveSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); // -------------------------- KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget->saveSettingsToGroup(&groupMapWidget); if (d->mapWidget2) { KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget 2"); d->mapWidget2->saveSettingsToGroup(&groupMapWidget); } KConfigGroup groupCorrelatorWidget = KConfigGroup(&group, "Correlator Widget"); d->correlatorWidget->saveSettingsToGroup(&groupCorrelatorWidget); KConfigGroup groupTreeView = KConfigGroup(&group, "Tree View"); d->treeView->saveSettingsToGroup(&groupTreeView); KConfigGroup groupSearchWidget = KConfigGroup(&group, "Search Widget"); d->searchWidget->saveSettingsToGroup(&groupSearchWidget); KConfigGroup groupRGWidget = KConfigGroup(&group, "Reverse Geocoding Widget"); d->rgWidget->saveSettingsToGroup(&groupRGWidget); // -------------------------- group.writeEntry("Current Tab", d->tabBar->currentIndex()); group.writeEntry("Show oldest images first", d->sortActionOldestFirst->isChecked()); group.writeEntry("SplitterState V1", d->VSplitter->saveState().toBase64()); group.writeEntry("SplitterState H1", d->HSplitter->saveState().toBase64()); group.writeEntry("Splitter H1 CollapsedSize", d->splitterSize); group.writeEntry("Map Layout", QVariant::fromValue(int(d->mapLayout))); group.writeEntry("Bookmarks visible", d->actionBookmarkVisibility->isChecked()); config->sync(); } void GeolocationEdit::closeEvent(QCloseEvent *e) { if (!e) return; // is the UI locked? if (!d->uiEnabled) { // please wait until we are done ... return; } // are there any modified images? int dirtyImagesCount = 0; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { const QModelIndex itemIndex = d->imageModel->index(i, 0); GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex); if (item->isDirty() || item->isTagListDirty()) { ++dirtyImagesCount; } } if (dirtyImagesCount > 0) { const QString message = i18np( "You have 1 modified image.", "You have %1 modified images.", dirtyImagesCount ); const int chosenAction = DMessageBox::showYesNo(QMessageBox::Warning, this, i18n("Unsaved changes"), i18n("%1 Would you like to save the changes you made to them?", message) ); if (chosenAction == QMessageBox::No) { saveSettings(); e->accept(); return; } if (chosenAction == QMessageBox::Yes) { // the user wants to save his changes. // this will initiate the saving process and then close the dialog. saveChanges(true); } // do not close the dialog for now e->ignore(); return; } saveSettings(); e->accept(); } void GeolocationEdit::slotImageActivated(const QModelIndex& index) { d->detailsWidget->slotSetCurrentImage(index); if (!index.isValid()) return; GPSItemContainer* const item = d->imageModel->itemFromIndex(index); if (!item) return; const GeoCoordinates imageCoordinates = item->coordinates(); if (imageCoordinates.hasCoordinates()) { d->mapWidget->setCenter(imageCoordinates); } } void GeolocationEdit::slotSetUIEnabled(const bool enabledState, QObject* const cancelObject, const QString& cancelSlot) { if (enabledState) { // hide the progress bar d->progressBar->setVisible(false); d->progressCancelButton->setVisible(false); d->progressBar->setProgressValue(d->progressBar->progressTotalSteps()); } // TODO: disable the worldmapwidget and the images list (at least disable editing operations) d->progressCancelObject = cancelObject; d->progressCancelSlot = cancelSlot; d->uiEnabled = enabledState; m_buttons->setEnabled(enabledState); d->correlatorWidget->setUIEnabledExternal(enabledState); d->detailsWidget->setUIEnabledExternal(enabledState); d->rgWidget->setUIEnabled(enabledState); d->treeView->setEditEnabled(enabledState); d->listViewContextMenu->setEnabled(enabledState); d->mapWidget->setAllowModifications(enabledState); } void GeolocationEdit::slotSetUIEnabled(const bool enabledState) { slotSetUIEnabled(enabledState, nullptr, QString()); } void GeolocationEdit::saveChanges(const bool closeAfterwards) { // TODO: actually save the changes // are there any modified images? QList dirtyImages; for (int i = 0 ; i < d->imageModel->rowCount() ; ++i) { const QModelIndex itemIndex = d->imageModel->index(i, 0); GPSItemContainer* const item = d->imageModel->itemFromIndex(itemIndex); if (item->isDirty() || item->isTagListDirty()) { dirtyImages << itemIndex; } } if (dirtyImages.isEmpty()) { if (closeAfterwards) { close(); } return; } // TODO: disable the UI and provide progress and cancel information slotSetUIEnabled(false); slotProgressSetup(dirtyImages.count(), i18n("Saving changes -")); // initiate the saving d->fileIOCountDone = 0; d->fileIOCountTotal = dirtyImages.count(); d->fileIOCloseAfterSaving = closeAfterwards; d->fileIOFutureWatcher = new QFutureWatcher >(this); connect(d->fileIOFutureWatcher, SIGNAL(resultsReadyAt(int,int)), this, SLOT(slotFileChangesSaved(int,int))); d->fileIOFuture = QtConcurrent::mapped(dirtyImages, SaveChangedImagesHelper(d->imageModel)); d->fileIOFutureWatcher->setFuture(d->fileIOFuture); } void GeolocationEdit::slotFileChangesSaved(int beginIndex, int endIndex) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << beginIndex << endIndex; d->fileIOCountDone += (endIndex-beginIndex); slotProgressChanged(d->fileIOCountDone); if (d->fileIOCountDone == d->fileIOCountTotal) { slotSetUIEnabled(true); // any errors? QList > errorList; for (int i = 0 ; i < d->fileIOFuture.resultCount() ; ++i) { if (!d->fileIOFuture.resultAt(i).second.isEmpty()) errorList << d->fileIOFuture.resultAt(i); // To rescan item metadata from host. emit signalMetadataChangedForUrl(d->fileIOFuture.resultAt(i).first); } if (!errorList.isEmpty()) { QStringList errorStrings; for (int i = 0 ; i < errorList.count() ; ++i) { errorStrings << QString::fromLatin1("%1: %2") .arg(errorList.at(i).first.toLocalFile()) .arg(errorList.at(i).second); } DMessageBox::showInformationList(QMessageBox::Critical, this, i18n("Error"), i18n("Failed to save some information:"), errorStrings); } // done saving files if (d->fileIOCloseAfterSaving) { close(); } } } void GeolocationEdit::slotApplyClicked() { // save the changes, but do not close afterwards saveChanges(false); } void GeolocationEdit::slotProgressChanged(const int currentProgress) { d->progressBar->setProgressValue(currentProgress); } void GeolocationEdit::slotProgressSetup(const int maxProgress, const QString& progressText) { d->progressBar->setProgressText(progressText); d->progressBar->setProgressTotalSteps(maxProgress); d->progressBar->setProgressValue(0); d->progressBar->setNotify(true); d->progressBar->setNotificationTitle(i18n("Edit Geolocation"), QIcon::fromTheme(QLatin1String("globe"))); d->progressBar->setVisible(true); d->progressCancelButton->setVisible(d->progressCancelObject != nullptr); } void GeolocationEdit::slotGPSUndoCommand(GPSUndoCommand* undoCommand) { d->undoStack->push(undoCommand); } void GeolocationEdit::slotSortOptionTriggered(QAction* /*sortAction*/) { int newSortKey = 0; if (d->sortActionOldestFirst->isChecked()) { newSortKey |= 1; } d->mapWidget->setSortKey(newSortKey); } void GeolocationEdit::slotProgressCancelButtonClicked() { if (d->progressCancelObject) { QTimer::singleShot(0, d->progressCancelObject, d->progressCancelSlot.toUtf8().constData()); d->progressBar->setProgressValue(d->progressBar->progressTotalSteps()); } } void GeolocationEdit::slotBookmarkVisibilityToggled() { d->bookmarkOwner->bookmarkModelHelper()->setVisible(d->actionBookmarkVisibility->isChecked()); } void GeolocationEdit::slotLayoutChanged(int lay) { d->mapLayout = (MapLayout)lay; adjustMapLayout(true); } MapWidget* GeolocationEdit::makeMapWidget(QWidget** const pvbox) { QWidget* const dummyWidget = new QWidget(this); QVBoxLayout* const vbox = new QVBoxLayout(dummyWidget); MapWidget* const mapWidget = new MapWidget(dummyWidget); mapWidget->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail); mapWidget->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup | MouseModeSelectThumbnail); mapWidget->setMouseMode(MouseModeSelectThumbnail); mapWidget->setGroupedModel(d->geoifaceMarkerModel); mapWidget->setDragDropHandler(d->mapDragDropHandler); mapWidget->addUngroupedModel(d->bookmarkOwner->bookmarkModelHelper()); mapWidget->addUngroupedModel(d->searchWidget->getModelHelper()); mapWidget->setTrackManager(d->trackManager); mapWidget->setSortOptionsMenu(d->sortMenu); vbox->addWidget(mapWidget); vbox->addWidget(mapWidget->getControlWidget()); QToolButton* const bookmarkVisibilityButton = new QToolButton(mapWidget); bookmarkVisibilityButton->setDefaultAction(d->actionBookmarkVisibility); mapWidget->addWidgetToControlWidget(bookmarkVisibilityButton); *pvbox = dummyWidget; return mapWidget; } void GeolocationEdit::adjustMapLayout(const bool syncSettings) { if (d->mapLayout == MapLayoutOne) { if (d->mapSplitter->count() > 1) { delete d->mapSplitter->widget(1); d->mapWidget2 = nullptr; } } else { if (d->mapSplitter->count() == 1) { QWidget* mapHolder = nullptr; d->mapWidget2 = makeMapWidget(&mapHolder); d->mapSplitter->addWidget(mapHolder); if (syncSettings) { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("Geolocation Edit Settings"); const KConfigGroup groupMapWidget = KConfigGroup(&group, "Map Widget"); d->mapWidget2->readSettingsFromGroup(&groupMapWidget); d->mapWidget2->setActive(true); } } if (d->mapLayout == MapLayoutHorizontal) { d->mapSplitter->setOrientation(Qt::Horizontal); } else { d->mapSplitter->setOrientation(Qt::Vertical); } } } } // namespace DigikamGenericGeolocationEditPlugin diff --git a/core/dplugins/generic/tools/calendar/print/calprinter.cpp b/core/dplugins/generic/tools/calendar/print/calprinter.cpp index 610512616d..1ed38c9e38 100644 --- a/core/dplugins/generic/tools/calendar/print/calprinter.cpp +++ b/core/dplugins/generic/tools/calendar/print/calprinter.cpp @@ -1,115 +1,115 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-11-13 * Description : printer thread. * * Copyright (C) 2008 by Orgad Shaneh * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "calprinter.h" // Qt includes #include // Local includes #include "calpainter.h" namespace DigikamGenericCalendarPlugin { class Q_DECL_HIDDEN CalPrinter::Private { public: explicit Private() : cancelled(false), printer(nullptr), painter(nullptr) { } bool cancelled; QMap months; QPrinter* printer; CalPainter* painter; }; CalPrinter::CalPrinter(QPrinter* const printer, QMap& months, QObject* const parent) : QThread(parent), d(new Private) { d->printer = printer; d->painter = new CalPainter(d->printer); d->months = months; d->cancelled = false; } CalPrinter::~CalPrinter() { delete d->painter; delete d; } void CalPrinter::run() { connect(d->painter, SIGNAL(signalTotal(int)), this, SIGNAL(totalBlocks(int))); connect(d->painter, SIGNAL(signalProgress(int)), this, SIGNAL(blocksFinished(int))); int currPage = 0; - foreach(const int month, d->months.keys()) + foreach (const int month, d->months.keys()) { emit pageChanged(currPage); if (currPage) { d->printer->newPage(); } ++currPage; d->painter->setImage(d->months.value(month)); d->painter->paint(month); if (d->cancelled) { break; } } d->painter->end(); emit pageChanged(currPage); } void CalPrinter::cancel() { d->painter->cancel(); d->cancelled = true; } } // Namespace Digikam diff --git a/core/dplugins/generic/tools/calendar/print/calsettings.cpp b/core/dplugins/generic/tools/calendar/print/calsettings.cpp index 4836809cad..832c62cfd1 100644 --- a/core/dplugins/generic/tools/calendar/print/calsettings.cpp +++ b/core/dplugins/generic/tools/calendar/print/calsettings.cpp @@ -1,363 +1,363 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-11-03 * Description : calendar parameters. * * Copyright (C) 2003-2005 by Renchi Raju * Copyright (C) 2007-2008 by Orgad Shaneh * Copyright (C) 2011 by Andi Clemens * Copyright (C) 2012 by Angelo Naselli * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "calsettings.h" // Local includes #include "digikam_debug.h" #include "calsystem.h" #ifdef HAVE_KCALENDAR // KCalCore includes # include # include # include // Qt includes # include #endif // HAVE_KCALENDAR namespace DigikamGenericCalendarPlugin { class Q_DECL_HIDDEN CalSettings::Private { public: explicit Private() { } QMap monthMap; QMap special; }; QPointer CalSettings::s_instance; CalSettings::CalSettings(QObject* const parent) : QObject(parent), d(new Private) { params.drawLines = false; params.year = CalSystem().earliestValidDate().year() + 1; setPaperSize(QLatin1String("A4")); setResolution(QLatin1String("High")); setImagePos(0); } CalSettings::~CalSettings() { delete d; } CalSettings* CalSettings::instance(QObject* const parent) { if (s_instance.isNull()) { s_instance = new CalSettings(parent); } return s_instance; } void CalSettings::setYear(int year) { params.year = year; emit settingsChanged(); } int CalSettings::year() const { return params.year; } void CalSettings::setImage(int month, const QUrl& path) { d->monthMap.insert(month, path); } QUrl CalSettings::image(int month) const { return d->monthMap.contains(month) ? d->monthMap[month] : QUrl(); } void CalSettings::setPaperSize(const QString& paperSize) { if (paperSize == QLatin1String("A4")) { params.paperWidth = 210; params.paperHeight = 297; params.pageSize = QPageSize::A4; } else if (paperSize == QLatin1String("US Letter")) { params.paperWidth = 216; params.paperHeight = 279; params.pageSize = QPageSize::Letter; } emit settingsChanged(); } void CalSettings::setResolution(const QString& resolution) { if (resolution == QLatin1String("High")) { params.printResolution = QPrinter::HighResolution; } else if (resolution == QLatin1String("Low")) { params.printResolution = QPrinter::ScreenResolution; } emit settingsChanged(); } void CalSettings::setImagePos(int pos) { const int previewSize = 300; switch (pos) { case CalParams::Top: { float zoom = qMin((float)previewSize / params.paperWidth, (float)previewSize / params.paperHeight); params.width = (int)(params.paperWidth * zoom); params.height = (int)(params.paperHeight * zoom); params.imgPos = CalParams::Top; break; } case CalParams::Left: { float zoom = qMin((float)previewSize / params.paperWidth, (float)previewSize / params.paperHeight); params.width = (int)(params.paperHeight * zoom); params.height = (int)(params.paperWidth * zoom); params.imgPos = CalParams::Left; break; } default: { float zoom = qMin((float)previewSize / params.paperWidth, (float)previewSize / params.paperHeight); params.width = (int)(params.paperHeight * zoom); params.height = (int)(params.paperWidth * zoom); params.imgPos = CalParams::Right; break; } } emit settingsChanged(); } void CalSettings::setDrawLines(bool draw) { if (params.drawLines != draw) { params.drawLines = draw; emit settingsChanged(); } } void CalSettings::setRatio(int ratio) { if (params.ratio != ratio) { params.ratio = ratio; emit settingsChanged(); } } void CalSettings::setFont(const QString& font) { if (params.baseFont.family() != font) { params.baseFont = QFont(font); emit settingsChanged(); } } void CalSettings::clearSpecial() { d->special.clear(); } void CalSettings::addSpecial(const QDate& date, const Day& info) { if (d->special.contains(date)) { d->special[date].second.append(QLatin1String("; ")).append(info.second); } else { d->special[date] = info; } } bool CalSettings::isPrayDay(const QDate& date) const { return (date.dayOfWeek() == Qt::Sunday); } /*! * \returns true if d->special formatting is to be applied to the particular day */ bool CalSettings::isSpecial(int month, int day) const { QDate dt = CalSystem().date(params.year, month, day); return (isPrayDay(dt) || d->special.contains(dt)); } /*! * \returns the color to be used for painting of the day info */ QColor CalSettings::getDayColor(int month, int day) const { QDate dt = CalSystem().date(params.year, month, day); if (isPrayDay(dt)) { return Qt::red; } if (d->special.contains(dt)) { return d->special[dt].first; } //default return Qt::black; } /*! * \returns the description of the day to be painted on the calendar. */ QString CalSettings::getDayDescr(int month, int day) const { QDate dt = CalSystem().date(params.year, month, day); QString ret; if (d->special.contains(dt)) { ret = d->special[dt].second; } return ret; } QPrinter::PrinterMode CalSettings::resolution() const { return params.printResolution; } #ifdef HAVE_KCALENDAR void CalSettings::loadSpecial(const QUrl& url, const QColor& color) { if (url.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Loading calendar from file failed: No valid url provided!"; return; } #ifdef HAVE_KCALENDAR_QDATETIME KCalCore::MemoryCalendar::Ptr memCal(new KCalCore::MemoryCalendar(QTimeZone::utc())); using DateTime = QDateTime; #else KCalCore::MemoryCalendar::Ptr memCal(new KCalCore::MemoryCalendar(QLatin1String("UTC"))); using DateTime = KDateTime; #endif KCalCore::FileStorage::Ptr fileStorage(new KCalCore::FileStorage(memCal, url.toLocalFile(), new KCalCore::ICalFormat)); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Loading calendar from file " << url.toLocalFile(); if (!fileStorage->load()) { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Failed to load calendar file!"; } else { CalSystem calSys; QDate qFirst, qLast; qFirst = calSys.date(params.year, 1, 1); qLast = calSys.date(params.year + 1, 1, 1); qLast = qLast.addDays(-1); DateTime dtFirst(qFirst, QTime(0, 0)); DateTime dtLast(qLast, QTime(0, 0)); DateTime dtCurrent; int counter = 0; KCalCore::Event::List list = memCal->rawEvents(qFirst, qLast); - foreach(const KCalCore::Event::Ptr event, list) + foreach (const KCalCore::Event::Ptr event, list) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << event->summary() << endl << "--------"; counter++; if (event->recurs()) { KCalCore::Recurrence* const recur = event->recurrence(); for (dtCurrent = recur->getNextDateTime(dtFirst.addDays(-1)); (dtCurrent <= dtLast) && dtCurrent.isValid(); dtCurrent = recur->getNextDateTime(dtCurrent)) { addSpecial(dtCurrent.date(), Day(color, event->summary())); } } else { addSpecial(event->dtStart().date(), Day(color, event->summary())); } } qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Loaded " << counter << " events"; memCal->close(); if (fileStorage->close()) { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Failed to close calendar file!"; } } } #endif // HAVE_KCALENDAR } // Namespace Digikam diff --git a/core/dplugins/generic/tools/expoblending/blendingdlg/enfusesettings.cpp b/core/dplugins/generic/tools/expoblending/blendingdlg/enfusesettings.cpp index 4236c67fee..125e2d39a2 100644 --- a/core/dplugins/generic/tools/expoblending/blendingdlg/enfusesettings.cpp +++ b/core/dplugins/generic/tools/expoblending/blendingdlg/enfusesettings.cpp @@ -1,285 +1,285 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-11-13 * Description : a tool to blend bracketed images. * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2015 by Benjamin Girault, * * 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, 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. * * ============================================================ */ #include "enfusesettings.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include #include #include namespace DigikamGenericExpoBlendingPlugin { QString EnfuseSettings::asCommentString() const { QString ret; ret.append(hardMask ? i18n("Hardmask: enabled") : i18n("Hardmask: disabled")); ret.append(QLatin1Char('\n')); ret.append(ciecam02 ? i18n("CIECAM02: enabled") : i18n("CIECAM02: disabled")); ret.append(QLatin1Char('\n')); ret.append(autoLevels ? i18n("Levels: auto") : i18n("Levels: %1", QString::number(levels))); ret.append(QLatin1Char('\n')); ret.append(i18n("Exposure: %1", exposure)); ret.append(QLatin1Char('\n')); ret.append(i18n("Saturation: %1", saturation)); ret.append(QLatin1Char('\n')); ret.append(i18n("Contrast: %1", contrast)); return ret; } QString EnfuseSettings::inputImagesList() const { QString ret; - foreach(const QUrl& url, inputUrls) + foreach (const QUrl& url, inputUrls) { ret.append(url.fileName() + QLatin1String(" ; ")); } ret.truncate(ret.length()-3); return ret; } class Q_DECL_HIDDEN EnfuseSettingsWidget::Private { public: explicit Private() : autoLevelsCB(nullptr), hardMaskCB(nullptr), ciecam02CB(nullptr), levelsLabel(nullptr), exposureLabel(nullptr), saturationLabel(nullptr), contrastLabel(nullptr), levelsInput(nullptr), exposureInput(nullptr), saturationInput(nullptr), contrastInput(nullptr) { } public: QCheckBox* autoLevelsCB; QCheckBox* hardMaskCB; QCheckBox* ciecam02CB; QLabel* levelsLabel; QLabel* exposureLabel; QLabel* saturationLabel; QLabel* contrastLabel; QSpinBox* levelsInput; QDoubleSpinBox* exposureInput; QDoubleSpinBox* saturationInput; QDoubleSpinBox* contrastInput; }; EnfuseSettingsWidget::EnfuseSettingsWidget(QWidget* const parent) : QWidget(parent), d(new Private) { setAttribute(Qt::WA_DeleteOnClose); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGridLayout* const grid = new QGridLayout(this); // ------------------------------------------------------------------------ d->autoLevelsCB = new QCheckBox(i18nc("@option:check Enfuse setting", "Automatic Local/Global Image Features Balance (Levels)"), this); d->autoLevelsCB->setToolTip(i18nc("@info:tooltip", "Optimize image features (contrast, saturation, . . .) to be as global as possible.")); d->autoLevelsCB->setWhatsThis(i18nc("@info:whatsthis", "Set automatic level selection (maximized) for pyramid blending, " "i.e. optimize image features (contrast, saturation, . . .) to be as global as possible.")); d->levelsLabel = new QLabel(i18nc("@label:slider Enfuse settings", "Image Features Balance:")); d->levelsInput = new QSpinBox(this); d->levelsInput->setRange(1, 29); d->levelsInput->setSingleStep(1); d->levelsInput->setToolTip(i18nc("@info:tooltip", "Balances between local features (small number) or global features (high number).")); d->levelsInput->setWhatsThis(i18nc("@info:whatsthis", "Set the number of levels for pyramid blending. " "Balances towards local features (small number) or global features (high number). " "Additionally, a low number trades off quality of results for faster " "execution time and lower memory usage.")); d->hardMaskCB = new QCheckBox(i18nc("@option:check", "Hard Mask"), this); d->hardMaskCB->setToolTip(i18nc("@info:tooltip", "Useful only for focus stack to improve sharpness.")); d->hardMaskCB->setWhatsThis(i18nc("@info:whatsthis", "Force hard blend masks without averaging on finest " "scale. This is only useful for focus " "stacks with thin and high contrast features. " "It improves sharpness at the expense of increased noise.")); d->exposureLabel = new QLabel(i18nc("@label:slider Enfuse settings", "Well-Exposedness Contribution:")); d->exposureInput = new QDoubleSpinBox(this); d->exposureInput->setRange(0.0, 1.0); d->exposureInput->setSingleStep(0.01); d->exposureInput->setToolTip(i18nc("@info:tooltip", "Contribution of well exposed pixels to the blending process.")); d->exposureInput->setWhatsThis(i18nc("@info:whatsthis", "Set the well-exposedness criterion contribution for the blending process. " "Higher values will favor well-exposed pixels.")); d->saturationLabel = new QLabel(i18nc("@label:slider enfuse settings", "High-Saturation Contribution:")); d->saturationInput = new QDoubleSpinBox(this); d->saturationInput->setDecimals(2); d->saturationInput->setRange(0.0, 1.0); d->saturationInput->setSingleStep(0.01); d->saturationInput->setToolTip(i18nc("@info:tooltip", "Contribution of highly saturated pixels to the blending process.")); d->saturationInput->setWhatsThis(i18nc("@info:whatsthis", "Increasing this value makes pixels with high " "saturation contribute more to the final output.")); d->contrastLabel = new QLabel(i18nc("@label:slider enfuse settings", "High-Contrast Contribution:")); d->contrastInput = new QDoubleSpinBox(this); d->contrastInput->setDecimals(2); d->contrastInput->setRange(0.0, 1.0); d->contrastInput->setSingleStep(0.01); d->contrastInput->setToolTip(i18nc("@info:tooltip", "Contribution of highly contrasted pixels to the blending process.")); d->contrastInput->setWhatsThis(i18nc("@info:whatsthis", "Sets the relative weight of high-contrast pixels. " "Increasing this weight makes pixels with neighboring differently colored " "pixels contribute more to the final output. Particularly useful for focus stacks.")); d->ciecam02CB = new QCheckBox(i18nc("@option:check", "Use Color Appearance Model (CIECAM02)"), this); d->ciecam02CB->setToolTip(i18nc("@info:tooltip", "Convert to CIECAM02 color appearance model during the blending process instead of RGB.")); d->ciecam02CB->setWhatsThis(i18nc("@info:whatsthis", "Use Color Appearance Modelling (CIECAM02) to render detailed colors. " "Your input files should have embedded ICC profiles. If no ICC profile is present, " "sRGB color space will be assumed. The difference between using this option " "and default color blending algorithm is very slight, and will be most noticeable " "when you need to blend areas of different primary colors together.")); // ------------------------------------------------------------------------ grid->addWidget(d->autoLevelsCB, 0, 0, 1, 2); grid->addWidget(d->levelsLabel, 1, 0, 1, 1); grid->addWidget(d->levelsInput, 1, 1, 1, 1); grid->addWidget(d->hardMaskCB, 2, 0, 1, 2); grid->addWidget(d->exposureLabel, 3, 0, 1, 1); grid->addWidget(d->exposureInput, 3, 1, 1, 1); grid->addWidget(d->saturationLabel, 4, 0, 1, 1); grid->addWidget(d->saturationInput, 4, 1, 1, 1); grid->addWidget(d->contrastLabel, 5, 0, 1, 1); grid->addWidget(d->contrastInput, 5, 1, 1, 1); grid->addWidget(d->ciecam02CB, 6, 0, 1, 2); grid->setRowStretch(7, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); // ------------------------------------------------------------------------ connect(d->autoLevelsCB, SIGNAL(toggled(bool)), d->levelsLabel, SLOT(setDisabled(bool))); connect(d->autoLevelsCB, SIGNAL(toggled(bool)), d->levelsInput, SLOT(setDisabled(bool))); } EnfuseSettingsWidget::~EnfuseSettingsWidget() { delete d; } void EnfuseSettingsWidget::resetToDefault() { d->autoLevelsCB->setChecked(true); d->levelsInput->setValue(20); d->hardMaskCB->setChecked(false); d->exposureInput->setValue(1.0); d->saturationInput->setValue(0.2); d->contrastInput->setValue(0.0); d->ciecam02CB->setChecked(false); } void EnfuseSettingsWidget::setSettings(const EnfuseSettings& settings) { d->autoLevelsCB->setChecked(settings.autoLevels); d->levelsInput->setValue(settings.levels); d->hardMaskCB->setChecked(settings.hardMask); d->exposureInput->setValue(settings.exposure); d->saturationInput->setValue(settings.saturation); d->contrastInput->setValue(settings.contrast); d->ciecam02CB->setChecked(settings.ciecam02); } EnfuseSettings EnfuseSettingsWidget::settings() const { EnfuseSettings settings; settings.autoLevels = d->autoLevelsCB->isChecked(); settings.levels = d->levelsInput->value(); settings.hardMask = d->hardMaskCB->isChecked(); settings.exposure = d->exposureInput->value(); settings.saturation = d->saturationInput->value(); settings.contrast = d->contrastInput->value(); settings.ciecam02 = d->ciecam02CB->isChecked(); return settings; } void EnfuseSettingsWidget::readSettings(KConfigGroup& group) { d->autoLevelsCB->setChecked(group.readEntry("Auto Levels", true)); d->levelsInput->setValue(group.readEntry("Levels Value", 20)); d->hardMaskCB->setChecked(group.readEntry("Hard Mask", false)); d->exposureInput->setValue(group.readEntry("Exposure Value", 1.0)); d->saturationInput->setValue(group.readEntry("Saturation Value", 0.2)); d->contrastInput->setValue(group.readEntry("Contrast Value", 0.0)); d->ciecam02CB->setChecked(group.readEntry("CIECAM02", false)); } void EnfuseSettingsWidget::writeSettings(KConfigGroup& group) { group.writeEntry("Auto Levels", d->autoLevelsCB->isChecked()); group.writeEntry("Levels Value", d->levelsInput->value()); group.writeEntry("Hard Mask", d->hardMaskCB->isChecked()); group.writeEntry("Exposure Value", d->exposureInput->value()); group.writeEntry("Saturation Value", d->saturationInput->value()); group.writeEntry("Contrast Value", d->contrastInput->value()); group.writeEntry("CIECAM02", d->ciecam02CB->isChecked()); } } // namespace DigikamGenericExpoBlendingPlugin diff --git a/core/dplugins/generic/tools/expoblending/blendingdlg/enfusestack.cpp b/core/dplugins/generic/tools/expoblending/blendingdlg/enfusestack.cpp index 019286bfc6..74b5db1e61 100644 --- a/core/dplugins/generic/tools/expoblending/blendingdlg/enfusestack.cpp +++ b/core/dplugins/generic/tools/expoblending/blendingdlg/enfusestack.cpp @@ -1,415 +1,415 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-13 * Description : a tool to blend bracketed images. * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2015 by Benjamin Girault, * * 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, 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. * * ============================================================ */ #include "enfusestack.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" #include "dworkingpixmap.h" using namespace Digikam; namespace DigikamGenericExpoBlendingPlugin { class Q_DECL_HIDDEN EnfuseStackItem::Private { public: explicit Private() : asValidThumb(false) { } bool asValidThumb; QPixmap thumb; EnfuseSettings settings; }; EnfuseStackItem::EnfuseStackItem(QTreeWidget* const parent) : QTreeWidgetItem(parent), d(new Private) { setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); setCheckState(0, Qt::Unchecked); setThumbnail(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(treeWidget()->iconSize().width(), QIcon::Disabled)); d->asValidThumb = false; } EnfuseStackItem::~EnfuseStackItem() { delete d; } void EnfuseStackItem::setEnfuseSettings(const EnfuseSettings& settings) { d->settings = settings; setText(1, d->settings.targetFileName); setText(2, d->settings.inputImagesList()); setToolTip(1, d->settings.asCommentString()); setToolTip(2, d->settings.inputImagesList().replace(QLatin1String(" ; "), QLatin1String("\n"))); } EnfuseSettings EnfuseStackItem::enfuseSettings() const { return d->settings; } const QUrl& EnfuseStackItem::url() const { return d->settings.previewUrl; } void EnfuseStackItem::setProgressAnimation(const QPixmap& pix) { QPixmap overlay = d->thumb; QPixmap mask(overlay.size()); mask.fill(QColor(128, 128, 128, 192)); QPainter p(&overlay); p.drawPixmap(0, 0, mask); p.drawPixmap((overlay.width()/2) - (pix.width()/2), (overlay.height()/2) - (pix.height()/2), pix); setIcon(0, QIcon(overlay)); } void EnfuseStackItem::setThumbnail(const QPixmap& pix) { int iconSize = qMax(treeWidget()->iconSize().width(), treeWidget()->iconSize().height()); QPixmap pixmap(iconSize+2, iconSize+2); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.drawPixmap((pixmap.width()/2) - (pix.width()/2), (pixmap.height()/2) - (pix.height()/2), pix); d->thumb = pixmap; setIcon(0, QIcon(pixmap)); d->asValidThumb = true; } void EnfuseStackItem::setProcessedIcon(const QIcon& icon) { setIcon(1, icon); setIcon(0, QIcon(d->thumb)); } bool EnfuseStackItem::asValidThumb() const { return d->asValidThumb; } bool EnfuseStackItem::isOn() const { return (checkState(0) == Qt::Checked ? true : false); } void EnfuseStackItem::setOn(bool b) { setCheckState(0, b ? Qt::Checked : Qt::Unchecked); } // ------------------------------------------------------------------------- class Q_DECL_HIDDEN EnfuseStackList::Private { public: explicit Private() : outputFormat(DSaveSettingsWidget::OUTPUT_PNG), progressCount(0), progressTimer(nullptr), progressPix(DWorkingPixmap()), processItem(nullptr) { } DSaveSettingsWidget::OutputFormat outputFormat; QString templateFileName; int progressCount; QTimer* progressTimer; DWorkingPixmap progressPix; EnfuseStackItem* processItem; }; EnfuseStackList::EnfuseStackList(QWidget* const parent) : QTreeWidget(parent), d(new Private) { d->progressTimer = new QTimer(this); setContextMenuPolicy(Qt::CustomContextMenu); setIconSize(QSize(64, 64)); setSelectionMode(QAbstractItemView::SingleSelection); setSortingEnabled(false); setAllColumnsShowFocus(true); setRootIsDecorated(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setColumnCount(3); setHeaderHidden(false); setDragEnabled(false); header()->setSectionResizeMode(QHeaderView::Stretch); QStringList labels; labels.append( i18nc("@title:column Saving checkbox", "Include during Saving") ); labels.append( i18nc("@title:column Output file name", "Output") ); labels.append( i18nc("@title:column Source file names", "Selected Inputs") ); setHeaderLabels(labels); connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotItemClicked(QTreeWidgetItem*))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotContextMenu(QPoint))); connect(d->progressTimer, SIGNAL(timeout()), this, SLOT(slotProgressTimerDone())); } EnfuseStackList::~EnfuseStackList() { delete d; } void EnfuseStackList::slotContextMenu(const QPoint& p) { QMenu popmenu(this); EnfuseStackItem* const item = dynamic_cast(itemAt(p)); if (item) { QAction* const rmItem = new QAction(QIcon::fromTheme(QLatin1String("window-close")), i18nc("@item:inmenu", "Remove item"), this); connect(rmItem, SIGNAL(triggered(bool)), this, SLOT(slotRemoveItem())); popmenu.addAction(rmItem); popmenu.addSeparator(); } QAction* const rmAll = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("@item:inmenu", "Clear all"), this); connect(rmAll, SIGNAL(triggered(bool)), this, SLOT(clear())); popmenu.addAction(rmAll); popmenu.exec(QCursor::pos()); } void EnfuseStackList::slotRemoveItem() { EnfuseStackItem* item = dynamic_cast(currentItem()); delete item; } QList EnfuseStackList::settingsList() { QList list; QTreeWidgetItemIterator it(this); while (*it) { EnfuseStackItem* const item = dynamic_cast(*it); if (item && item->isOn()) { list.append(item->enfuseSettings()); } ++it; } return list; } void EnfuseStackList::clearSelected() { QList list; QTreeWidgetItemIterator it(this); while (*it) { EnfuseStackItem* const item = dynamic_cast(*it); if (item && item->isOn()) { list.append(item); } ++it; } - foreach(QTreeWidgetItem* const item, list) + foreach (QTreeWidgetItem* const item, list) delete item; } void EnfuseStackList::setOnItem(const QUrl& url, bool on) { EnfuseStackItem* const item = findItemByUrl(url); if (item) item->setOn(on); } void EnfuseStackList::removeItem(const QUrl& url) { EnfuseStackItem* const item = findItemByUrl(url); delete item; } void EnfuseStackList::addItem(const QUrl& url, const EnfuseSettings& settings) { if (!url.isValid()) return; // Check if the new item already exist in the list. if (!findItemByUrl(url)) { EnfuseSettings enfusePrms = settings; QString ext = DSaveSettingsWidget::extensionForFormat(enfusePrms.outputFormat); enfusePrms.previewUrl = url; EnfuseStackItem* const item = new EnfuseStackItem(this); item->setEnfuseSettings(enfusePrms); item->setOn(true); setCurrentItem(item); setTemplateFileName(d->outputFormat, d->templateFileName); emit signalItemClicked(url); } } void EnfuseStackList::setThumbnail(const QUrl& url, const QImage& img) { if (img.isNull()) return; EnfuseStackItem* const item = findItemByUrl(url); if (item && (!item->asValidThumb())) item->setThumbnail(QPixmap::fromImage(img.scaled(iconSize().width(), iconSize().height(), Qt::KeepAspectRatio))); } void EnfuseStackList::slotItemClicked(QTreeWidgetItem* item) { EnfuseStackItem* const eItem = dynamic_cast(item); if (eItem) emit signalItemClicked(eItem->url()); } void EnfuseStackList::slotProgressTimerDone() { d->processItem->setProgressAnimation(d->progressPix.frameAt(d->progressCount)); d->progressCount++; if (d->progressCount == 8) d->progressCount = 0; d->progressTimer->start(300); } EnfuseStackItem* EnfuseStackList::findItemByUrl(const QUrl& url) { QTreeWidgetItemIterator it(this); while (*it) { EnfuseStackItem* const item = dynamic_cast(*it); if (item && (item->url() == url)) return item; ++it; } return nullptr; } void EnfuseStackList::processingItem(const QUrl& url, bool run) { d->processItem = findItemByUrl(url); if (d->processItem) { if (run) { setCurrentItem(d->processItem, true); scrollToItem(d->processItem); d->progressTimer->start(300); } else { d->progressTimer->stop(); d->processItem = nullptr; } } } void EnfuseStackList::processedItem(const QUrl& url, bool success) { EnfuseStackItem* const item = findItemByUrl(url); if (item) item->setProcessedIcon(QIcon::fromTheme(success ? QLatin1String("dialog-ok-apply") : QLatin1String("dialog-cancel"))); } void EnfuseStackList::setTemplateFileName(DSaveSettingsWidget::OutputFormat frm, const QString& string) { d->outputFormat = frm; d->templateFileName = string; int count = 0; QTreeWidgetItemIterator it(this); while (*it) { EnfuseStackItem* const item = dynamic_cast(*it); if (item) { QString temp; EnfuseSettings settings = item->enfuseSettings(); QString ext = DSaveSettingsWidget::extensionForFormat(d->outputFormat); settings.outputFormat = d->outputFormat; settings.targetFileName = d->templateFileName + temp.sprintf("-%02i", count+1).append(ext); item->setEnfuseSettings(settings); } ++it; count++; } } } // namespace DigikamGenericExpoBlendingPlugin diff --git a/core/dplugins/generic/tools/htmlgallery/generator/gallerygenerator.cpp b/core/dplugins/generic/tools/htmlgallery/generator/gallerygenerator.cpp index f8cdbbb064..f3edf6851d 100644 --- a/core/dplugins/generic/tools/htmlgallery/generator/gallerygenerator.cpp +++ b/core/dplugins/generic/tools/htmlgallery/generator/gallerygenerator.cpp @@ -1,633 +1,633 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-04-04 * Description : a tool to generate HTML image galleries * * Copyright (C) 2006-2010 by Aurelien Gateau * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "gallerygenerator.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // libxslt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "abstractthemeparameter.h" #include "galleryelement.h" #include "galleryelementfunctor.h" #include "galleryinfo.h" #include "gallerytheme.h" #include "galleryxmlutils.h" #include "htmlwizard.h" #include "dfileoperations.h" namespace DigikamGenericHtmlGalleryPlugin { typedef QMap XsltParameterMap; class Q_DECL_HIDDEN GalleryGenerator::Private { public: // Url => local temp path typedef QHash RemoteUrlHash; public: explicit Private() : that(nullptr), info(nullptr), warnings(false), cancel(false), pview(nullptr), pbar(nullptr) { } GalleryGenerator* that; GalleryInfo* info; GalleryTheme::Ptr theme; // State info bool warnings; QString xmlFileName; bool cancel; DHistoryView* pview; DProgressWdg* pbar; public: bool init() { cancel = false; theme = GalleryTheme::findByInternalName(info->theme()); if (!theme) { logError( i18n("Could not find theme in '%1'", info->theme()) ); return false; } pview->setVisible(true); pbar->setVisible(true); return true; } bool createDir(const QString& dirName) { logInfo(i18n("Create directories")); if (!QDir().mkpath(dirName)) { logError(i18n("Could not create folder '%1'", QDir::toNativeSeparators(dirName))); return false; } return true; } bool copyTheme() { logInfo(i18n("Copying theme")); QUrl srcUrl = QUrl::fromLocalFile(theme->directory()); QUrl destUrl = info->destUrl().adjusted(QUrl::StripTrailingSlash); QDir themeDir(destUrl.toLocalFile() + QLatin1Char('/') + srcUrl.fileName()); if (themeDir.exists()) { themeDir.removeRecursively(); } bool ok = DFileOperations::copyFolderRecursively(srcUrl.toLocalFile(), destUrl.toLocalFile()); if (!ok) { logError(i18n("Could not copy theme")); return false; } return true; } bool generateImagesAndXML() { logInfo(i18n("Generate images and XML files")); QString baseDestDir = info->destUrl().toLocalFile(); if (!createDir(baseDestDir)) return false; xmlFileName = baseDestDir + QLatin1String("/gallery.xml"); XMLWriter xmlWriter; if (!xmlWriter.open(xmlFileName)) { logError(i18n("Could not create gallery.xml")); return false; } XMLElement collectionsX(xmlWriter, QLatin1String("collections")); if (info->m_getOption == GalleryInfo::ALBUMS) { // Loop over albums selection DInfoInterface::DAlbumIDs::ConstIterator albumIt = info->m_albumList.constBegin(); DInfoInterface::DAlbumIDs::ConstIterator albumEnd = info->m_albumList.constEnd(); for (; albumIt != albumEnd ; ++albumIt) { int id = *albumIt; DInfoInterface::DInfoMap inf; if (info->m_iface) inf = info->m_iface->albumInfo(id); DAlbumInfo anf(inf); QString title = anf.title(); QString collectionFileName = webifyFileName(title); QString destDir = baseDestDir + QLatin1Char('/') + collectionFileName; if (!createDir(destDir)) { return false; } XMLElement collectionX(xmlWriter, QLatin1String("collection")); xmlWriter.writeElement("name", title); xmlWriter.writeElement("fileName", collectionFileName); xmlWriter.writeElement("comment", anf.caption()); // Gather image element list QList imageList; if (info->m_iface) { imageList = info->m_iface->albumsItems(DInfoInterface::DAlbumIDs() << id); } if (!processImages(xmlWriter, imageList, title, destDir)) return false; } } else { QString title = info->imageSelectionTitle(); QString collectionFileName = webifyFileName(title); QString destDir = baseDestDir + QLatin1Char('/') + collectionFileName; if (!createDir(destDir)) { return false; } XMLElement collectionX(xmlWriter, QLatin1String("collection")); xmlWriter.writeElement("name", title); xmlWriter.writeElement("fileName", collectionFileName); if (!processImages(xmlWriter, info->m_imageList, title, destDir)) return false; } return true; } bool processImages(XMLWriter& xmlWriter, const QList& imageList, const QString& title, const QString& destDir) { RemoteUrlHash remoteUrlHash; if (!downloadRemoteUrls(title, imageList, &remoteUrlHash)) { return false; } QList imageElementList; - foreach(const QUrl& url, imageList) + foreach (const QUrl& url, imageList) { const QString path = remoteUrlHash.value(url, url.toLocalFile()); if (path.isEmpty()) { continue; } DInfoInterface::DInfoMap inf; if (info->m_iface) inf = info->m_iface->itemInfo(url); GalleryElement element = GalleryElement(inf); element.m_path = remoteUrlHash.value(url, url.toLocalFile()); imageElementList << element; } // Generate images logInfo(i18n("Generating files for \"%1\"", title)); GalleryElementFunctor functor(that, info, destDir); QFuture future = QtConcurrent::map(imageElementList, functor); QFutureWatcher watcher; watcher.setFuture(future); connect(&watcher, SIGNAL(progressValueChanged(int)), pbar, SLOT(setValue(int))); pbar->setMaximum(imageElementList.count()); while (!future.isFinished()) { qApp->processEvents(); if (cancel) { future.cancel(); future.waitForFinished(); return false; } } // Generate xml - foreach(const GalleryElement& element, imageElementList) + foreach (const GalleryElement& element, imageElementList) { element.appendToXML(xmlWriter, info->copyOriginalImage()); } return true; } bool generateHTML() { logInfo(i18n("Generating HTML files")); QString xsltFileName = theme->directory() + QLatin1String("/template.xsl"); CWrapper xslt = xsltParseStylesheetFile((const xmlChar*) QDir::toNativeSeparators(xsltFileName).toUtf8().data()); if (!xslt) { logError(i18n("Could not load XSL file '%1'", xsltFileName)); return false; } CWrapper xmlGallery = xmlParseFile(QDir::toNativeSeparators(xmlFileName).toUtf8().data() ); if (!xmlGallery) { logError(i18n("Could not load XML file '%1'", xmlFileName)); return false; } // Prepare parameters XsltParameterMap map; addI18nParameters(map); addThemeParameters(map); const char** params = new const char*[map.size()*2+1]; XsltParameterMap::Iterator it = map.begin(); XsltParameterMap::Iterator end = map.end(); const char** ptr = params; for ( ; it != end ; ++it) { *ptr = it.key().data(); ++ptr; *ptr = it.value().data(); ++ptr; } *ptr = nullptr; // Move to the destination dir, so that external documents get correctly // produced QString oldCD = QDir::currentPath(); QDir::setCurrent(info->destUrl().toLocalFile()); CWrapper xmlOutput = xsltApplyStylesheet(xslt, xmlGallery, params); QDir::setCurrent(oldCD); //delete []params; if (!xmlOutput) { logError(i18n("Error processing XML file")); return false; } QString destFileName = QDir::toNativeSeparators(info->destUrl().toLocalFile() + QLatin1String("/index.html")); if (xsltSaveResultToFilename(destFileName.toUtf8().data(), xmlOutput, xslt, 0) == -1) { logError(i18n("Could not open '%1' for writing", destFileName)); return false; } return true; } bool downloadRemoteUrls(const QString& collectionName, const QList& _list, RemoteUrlHash* const hash) { Q_ASSERT(hash); QList list; - foreach(const QUrl& url, _list) + foreach (const QUrl& url, _list) { if (!url.isLocalFile()) { list << url; } } if (list.count() == 0) { return true; } logInfo(i18n("Downloading remote files for \"%1\"", collectionName)); pbar->setMaximum(list.count()); int count = 0; - foreach(const QUrl& url, list) + foreach (const QUrl& url, list) { if (cancel) { return false; } QTemporaryFile tempFile; tempFile.setFileTemplate(QLatin1String("htmlgallery-")); if (!tempFile.open()) { logError(i18n("Could not open temporary file")); return false; } QTemporaryFile tempPath; tempPath.setFileTemplate(tempFile.fileName()); tempPath.setAutoRemove(false); if (tempPath.open() && DFileOperations::copyFiles(QStringList() << url.toLocalFile(), tempPath.fileName())) { hash->insert(url, tempFile.fileName()); } else { logWarning(i18n("Could not download %1", url.toDisplayString())); hash->insert(url, QString()); } tempPath.close(); tempFile.close(); ++count; pbar->setValue(count); } return true; } /** * Add to map all the i18n parameters. */ void addI18nParameters(XsltParameterMap& map) { map["i18nPrevious"] = makeXsltParam(i18n("Previous")); map["i18nNext"] = makeXsltParam(i18n("Next")); map["i18nCollectionList"] = makeXsltParam(i18n("Album List")); map["i18nOriginalImage"] = makeXsltParam(i18n("Original Image")); map["i18nUp"] = makeXsltParam(i18n("Go Up")); // Exif Tag map["i18nexifimagemake"] = makeXsltParam(i18n("Make")); map["i18nexifimagemodel"] = makeXsltParam(i18n("Model")); map["i18nexifimageorientation"] = makeXsltParam(i18n("Image Orientation")); map["i18nexifimagexresolution"] = makeXsltParam(i18n("Image X Resolution")); map["i18nexifimageyresolution"] = makeXsltParam(i18n("Image Y Resolution")); map["i18nexifimageresolutionunit"] = makeXsltParam(i18n("Image Resolution Unit")); map["i18nexifimagedatetime"] = makeXsltParam(i18n("Image Date Time")); map["i18nexifimageycbcrpositioning"] = makeXsltParam(i18n("YCBCR Positioning")); map["i18nexifphotoexposuretime"] = makeXsltParam(i18n("Exposure Time")); map["i18nexifphotofnumber"] = makeXsltParam(i18n("F Number")); map["i18nexifphotoexposureprogram"] = makeXsltParam(i18n("Exposure Index")); map["i18nexifphotoisospeedratings"] = makeXsltParam(i18n("ISO Speed Ratings")); map["i18nexifphotoshutterspeedvalue"] = makeXsltParam(i18n("Shutter Speed Value")); map["i18nexifphotoaperturevalue"] = makeXsltParam(i18n("Aperture Value")); map["i18nexifphotofocallength"] = makeXsltParam(i18n("Focal Length")); map["i18nexifgpsaltitude"] = makeXsltParam(i18n("GPS Altitude")); map["i18nexifgpslatitude"] = makeXsltParam(i18n("GPS Latitude")); map["i18nexifgpslongitude"] = makeXsltParam(i18n("GPS Longitude")); } /** * Add to map all the theme parameters, as specified by the user. */ void addThemeParameters(XsltParameterMap& map) { GalleryTheme::ParameterList parameterList = theme->parameterList(); QString themeInternalName = theme->internalName(); GalleryTheme::ParameterList::ConstIterator it = parameterList.constBegin(); GalleryTheme::ParameterList::ConstIterator end = parameterList.constEnd(); for (; it != end ; ++it) { AbstractThemeParameter* const themeParameter = *it; QByteArray internalName = themeParameter->internalName(); QString value = info->getThemeParameterValue(themeInternalName, QLatin1String(internalName), themeParameter->defaultValue()); map[internalName] = makeXsltParam(value); } } /** * Prepare an XSLT param, managing quote mess. * abc => 'abc' * a"bc => 'a"bc' * a'bc => "a'bc" * a"'bc => concat('a"', "'", 'bc') */ QByteArray makeXsltParam(const QString& txt) { QString param; static const char apos = '\''; static const char quote = '"'; if (txt.indexOf(QLatin1Char(apos)) == -1) { // First or second case: no apos param = QLatin1Char(apos) + txt + QLatin1Char(apos); } else if (txt.indexOf(QLatin1Char(quote)) == -1) { // Third case: only apos, no quote param = QLatin1Char(quote) + txt + QLatin1Char(quote); } else { // Forth case: both apos and quote :-( const QStringList lst = txt.split(QLatin1Char(apos), QString::KeepEmptyParts); QStringList::ConstIterator it = lst.constBegin(); QStringList::ConstIterator end = lst.constEnd(); param = QLatin1String("concat("); param += QLatin1Char(apos) + *it + QLatin1Char(apos); ++it; for (; it != end ; ++it) { param += QLatin1String(", \"'\", "); param += QLatin1Char(apos) + *it + QLatin1Char(apos); } param += QLatin1Char(')'); } //qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "param: " << txt << " => " << param; return param.toUtf8(); } void logInfo(const QString& msg) { pview->addEntry(msg, DHistoryView::ProgressEntry); } void logError(const QString& msg) { pview->addEntry(msg, DHistoryView::ErrorEntry); } void logWarning(const QString& msg) { pview->addEntry(msg, DHistoryView::WarningEntry); warnings = true; } }; // ---------------------------------------------------------------------- GalleryGenerator::GalleryGenerator(GalleryInfo* const info) : QObject(), d(new Private) { d->that = this; d->info = info; d->warnings = false; connect(this, SIGNAL(logWarningRequested(QString)), SLOT(logWarning(QString)), Qt::QueuedConnection); } GalleryGenerator::~GalleryGenerator() { delete d; } bool GalleryGenerator::run() { if (!d->init()) return false; QString destDir = d->info->destUrl().toLocalFile(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << destDir; if (!d->createDir(destDir)) return false; if (!d->copyTheme()) return false; if (!d->generateImagesAndXML()) return false; exsltRegisterAll(); bool result = d->generateHTML(); xsltCleanupGlobals(); xmlCleanupParser(); return result; } bool GalleryGenerator::warnings() const { return d->warnings; } void GalleryGenerator::logWarning(const QString& text) { d->logWarning(text); } void GalleryGenerator::slotCancel() { d->cancel = true; } void GalleryGenerator::setProgressWidgets(DHistoryView* const pView, DProgressWdg* const pBar) { d->pview = pView; d->pbar = pBar; connect(d->pbar, SIGNAL(signalProgressCanceled()), this, SLOT(slotCancel())); } QString GalleryGenerator::webifyFileName(const QString& fname) { QString fileName = fname.toLower(); // Remove potentially troublesome chars return fileName.replace(QRegExp(QLatin1String("[^-0-9a-z]+")), QLatin1String("_")); } } // namespace DigikamGenericHtmlGalleryPlugin diff --git a/core/dplugins/generic/tools/htmlgallery/wizard/htmlfinalpage.cpp b/core/dplugins/generic/tools/htmlgallery/wizard/htmlfinalpage.cpp index 0027e3f28c..37bf13e270 100644 --- a/core/dplugins/generic/tools/htmlgallery/wizard/htmlfinalpage.cpp +++ b/core/dplugins/generic/tools/htmlgallery/wizard/htmlfinalpage.cpp @@ -1,203 +1,203 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-04-04 * Description : a tool to generate HTML image galleries * * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "htmlfinalpage.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "htmlwizard.h" #include "abstractthemeparameter.h" #include "galleryinfo.h" #include "gallerygenerator.h" #include "dlayoutbox.h" #include "digikam_debug.h" #include "dprogresswdg.h" #include "dhistoryview.h" #include "webbrowserdlg.h" namespace DigikamGenericHtmlGalleryPlugin { class Q_DECL_HIDDEN HTMLFinalPage::Private { public: explicit Private() : progressView(nullptr), progressBar(nullptr), complete(false) { } DHistoryView* progressView; DProgressWdg* progressBar; bool complete; }; HTMLFinalPage::HTMLFinalPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private) { setObjectName(QLatin1String("FinalPage")); DVBox* const vbox = new DVBox(this); d->progressView = new DHistoryView(vbox); d->progressBar = new DProgressWdg(vbox); vbox->setStretchFactor(d->progressBar, 10); vbox->setContentsMargins(QMargins()); vbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setPageWidget(vbox); setLeftBottomPix(QIcon::fromTheme(QLatin1String("system-run"))); } HTMLFinalPage::~HTMLFinalPage() { delete d; } void HTMLFinalPage::initializePage() { d->complete = false; emit completeChanged(); QTimer::singleShot(0, this, SLOT(slotProcess())); } void HTMLFinalPage::slotProcess() { HTMLWizard* const wizard = dynamic_cast(assistant()); if (!wizard) { d->progressView->addEntry(i18n("Internal Error"), DHistoryView::ErrorEntry); return; } d->progressView->clear(); d->progressBar->reset(); GalleryInfo* const info = wizard->galleryInfo(); // Generate GalleryInfo qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << info; d->progressView->addEntry(i18n("Starting to generate gallery..."), DHistoryView::ProgressEntry); if (info->m_getOption == GalleryInfo::ALBUMS) { if (!info->m_iface) return; d->progressView->addEntry(i18n("%1 albums to process:", info->m_albumList.count()), DHistoryView::ProgressEntry); - foreach(const QUrl& url, info->m_iface->albumsItems(info->m_albumList)) + foreach (const QUrl& url, info->m_iface->albumsItems(info->m_albumList)) { d->progressView->addEntry(QDir::toNativeSeparators(url.toLocalFile()), DHistoryView::ProgressEntry); } } else { d->progressView->addEntry(i18n("%1 items to process", info->m_imageList.count()), DHistoryView::ProgressEntry); } d->progressView->addEntry(i18n("Output directory: %1", QDir::toNativeSeparators(info->destUrl().toLocalFile())), DHistoryView::ProgressEntry); GalleryGenerator generator(info); generator.setProgressWidgets(d->progressView, d->progressBar); if (!generator.run()) { return; } if (generator.warnings()) { d->progressView->addEntry(i18n("Gallery is completed, but some warnings occurred."), DHistoryView::WarningEntry); } else { d->progressView->addEntry(i18n("Gallery completed."), DHistoryView::ProgressEntry); } QUrl url = info->destUrl().adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/index.html")); switch (info->openInBrowser()) { case GalleryConfig::DESKTOP: { QDesktopServices::openUrl(url); d->progressView->addEntry(i18n("Opening gallery with default desktop browser..."), DHistoryView::ProgressEntry); break; } case GalleryConfig::INTERNAL: { WebBrowserDlg* const browser = new WebBrowserDlg(url, this); browser->show(); d->progressView->addEntry(i18n("Opening gallery with internal browser..."), DHistoryView::ProgressEntry); break; } default: break; } d->complete = true; emit completeChanged(); } bool HTMLFinalPage::isComplete() const { return d->complete; } } // namespace DigikamGenericHtmlGalleryPlugin diff --git a/core/dplugins/generic/tools/mediaserver/server/dlnaserverdelegate.cpp b/core/dplugins/generic/tools/mediaserver/server/dlnaserverdelegate.cpp index 02560b679c..481ab8c19f 100644 --- a/core/dplugins/generic/tools/mediaserver/server/dlnaserverdelegate.cpp +++ b/core/dplugins/generic/tools/mediaserver/server/dlnaserverdelegate.cpp @@ -1,843 +1,843 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-09-24 * Description : a media server to export collections through DLNA. * Implementation inspired on Platinum File Media Server. * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "dlnaserverdelegate.h" // Platinum includes #include "NptStreams.h" #include "PltUPnP.h" #include "PltMediaItem.h" #include "PltService.h" #include "PltTaskManager.h" #include "PltHttpServer.h" #include "PltDidl.h" #include "PltVersion.h" #include "PltMimeType.h" // Qt includes #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "previewloadthread.h" #include "dimg.h" #include "drawdecoder.h" NPT_SET_LOCAL_LOGGER("digiKam.media.server.delegate") namespace Digikam { class Q_DECL_HIDDEN DLNAMediaServerDelegate::Private { public: explicit Private() : filterUnknownOut(false), useCache(false) { } NPT_String urlRoot; NPT_String fileRoot; bool filterUnknownOut; bool useCache; MediaServerMap map; PLT_MediaCache >, NPT_TimeStamp> dirCache; }; DLNAMediaServerDelegate::DLNAMediaServerDelegate(const char* url_root, bool use_cache) : d(new Private) { d->urlRoot = url_root; d->useCache = use_cache; } DLNAMediaServerDelegate::~DLNAMediaServerDelegate() { delete d; } void DLNAMediaServerDelegate::addAlbumsOnServer(const MediaServerMap& map) { d->map = map; } NPT_Result DLNAMediaServerDelegate::ProcessFileRequest(NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response) { NPT_HttpUrlQuery query(request.GetUrl().GetQuery()); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, "DLNAMediaServerDelegate::ProcessFileRequest:", &request); if (request.GetMethod().Compare("GET") && request.GetMethod().Compare("HEAD")) { response.SetStatus(500, "Internal Server Error"); return NPT_SUCCESS; } // Extract file path from url NPT_String file_path; NPT_CHECK_LABEL_WARNING(ExtractResourcePath(request.GetUrl(), file_path), failure); // Serve file NPT_CHECK_WARNING(ServeFile(request, context, response, NPT_FilePath::Create(d->fileRoot, file_path))); return NPT_SUCCESS; // cppcheck-suppress unusedLabel failure: response.SetStatus(404, "File Not Found"); return NPT_SUCCESS; } NPT_Result DLNAMediaServerDelegate::OnBrowseMetadata(PLT_ActionReference& action, const char* object_id, const char* filter, NPT_UInt32 starting_index, NPT_UInt32 requested_count, const char* sort_criteria, const PLT_HttpRequestContext& context) { NPT_COMPILER_UNUSED(sort_criteria); NPT_COMPILER_UNUSED(requested_count); NPT_COMPILER_UNUSED(starting_index); NPT_String didl; PLT_MediaObjectReference item; // Locate the file from the object ID NPT_String filepath; if (NPT_FAILED(GetFilePath(object_id, filepath))) { // error qCDebug(DIGIKAM_MEDIASRV_LOG) << "OnBrowseMetadata()" << ":: ObjectID not found \"" << object_id << "\""; action->SetError(701, "No Such Object."); return NPT_FAILURE; } // build the object didl item = BuildFromFilePath(filepath, context, true, false, (NPT_String(filter).Find("ALLIP") != -1)); if (item.IsNull()) { return NPT_FAILURE; } NPT_String tmp; NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp)); // add didl header and footer didl = didl_header + tmp + didl_footer; NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl)); NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", "1")); NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", "1")); // update ID may be wrong here, it should be the one of the container? // TODO: We need to keep track of the overall updateID of the CDS NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1")); return NPT_SUCCESS; } NPT_Result DLNAMediaServerDelegate::OnBrowseDirectChildren(PLT_ActionReference& action, const char* object_id, const char* filter, NPT_UInt32 starting_index, NPT_UInt32 requested_count, const char* sort_criteria, const PLT_HttpRequestContext& context) { NPT_COMPILER_UNUSED(sort_criteria); // Locate the file from the object ID NPT_String dir; NPT_FileInfo info; if (NPT_FAILED(GetFilePath(object_id, dir))) { // error NPT_LOG_WARNING_1("ObjectID \'%s\' not found or not allowed", object_id); action->SetError(701, "No such Object"); NPT_CHECK_WARNING(NPT_FAILURE); } qCDebug(DIGIKAM_MEDIASRV_LOG) << "OnBrowseDirectChildren() :: Object id:" << object_id << "Dir:" << dir.GetChars(); // get uuid from device via action reference NPT_String uuid = action->GetActionDesc().GetService()->GetDevice()->GetUUID(); // Try to get list from cache if allowed NPT_Reference > entries; NPT_TimeStamp cached_entries_time; if (!d->useCache || NPT_FAILED(d->dirCache.Get(uuid, dir, entries, &cached_entries_time)) || cached_entries_time < info.m_ModificationTime) { // if not found in cache or if current dir has newer modified time fetch fresh new list from source QStringList list; if (dir == "/") { - foreach(const QString& s, d->map.keys()) + foreach (const QString& s, d->map.keys()) { list << s + QLatin1Char('/'); } } else { QString container = QString::fromUtf8(dir.GetChars()); QList urls = d->map.value(container.remove(QLatin1Char('/'))); - foreach(const QUrl& u, urls) + foreach (const QUrl& u, urls) { // Internal URL separator between container path and local file path. // Ex: Linux => "/country/town/Paris/?file:/mnt/data/travel/Paris/eiffeltower.jpg // Win32 => "/Friends/US/Brown/?file:C:/Users/Foo/My Images/Friends/US/Brown/homer.png list << QLatin1String("?file:") + u.toLocalFile(); } } qCDebug(DIGIKAM_MEDIASRV_LOG) << "OnBrowseDirectChildren() ::" << "Populate cache with contents from Dir" << dir.GetChars(); entries = new NPT_List(); - foreach(const QString& path, list) + foreach (const QString& path, list) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "=>" << path; entries->Add(NPT_String(path.toUtf8().data(), path.toUtf8().size())); } // add new list to cache if (d->useCache) { d->dirCache.Put(uuid, dir, entries, &info.m_ModificationTime); } } unsigned long cur_index = 0; unsigned long num_returned = 0; unsigned long total_matches = 0; NPT_String didl = didl_header; bool allip = (NPT_String(filter).Find("ALLIP") != -1); PLT_MediaObjectReference item; for (NPT_List::Iterator it = entries->GetFirstItem() ; it ; ++it) { NPT_String filepath = dir + (*it); // verify we want to process this file first if (!ProcessFile(filepath, filter)) { continue; } qCDebug(DIGIKAM_MEDIASRV_LOG) << "OnBrowseDirectChildren()" << ":: Process item" << filepath.GetChars(); // build item object from file path item = BuildFromFilePath(filepath, context, true, true, allip); // generate didl if within range requested if (!item.IsNull()) { if ((cur_index >= starting_index) && ((num_returned < requested_count) || (requested_count == 0))) { NPT_String tmp; NPT_CHECK_SEVERE(PLT_Didl::ToDidl(*item.AsPointer(), filter, tmp)); didl += tmp; ++num_returned; } ++cur_index; ++total_matches; } }; didl += didl_footer; NPT_LOG_FINE_6("BrowseDirectChildren from %s returning %d-%d/%d objects (%d out of %d requested)", (const char*)context.GetLocalAddress().GetIpAddress().ToString(), starting_index, starting_index + num_returned, total_matches, num_returned, requested_count); NPT_CHECK_SEVERE(action->SetArgumentValue("Result", didl)); NPT_CHECK_SEVERE(action->SetArgumentValue("NumberReturned", NPT_String::FromInteger(num_returned))); // 0 means we don't know how many we have but most browsers don't like that!! NPT_CHECK_SEVERE(action->SetArgumentValue("TotalMatches", NPT_String::FromInteger(total_matches))); NPT_CHECK_SEVERE(action->SetArgumentValue("UpdateId", "1")); return NPT_SUCCESS; } PLT_MediaObject* DLNAMediaServerDelegate::BuildFromFilePath(const NPT_String& filepath, const PLT_HttpRequestContext& context, bool with_count, bool keep_extension_in_title, bool allip) { PLT_MediaItemResource resource; PLT_MediaObject* object = nullptr; qCDebug(DIGIKAM_MEDIASRV_LOG) << "Building didl for file \"" << filepath.GetChars() << "\""; // retrieve the entry type (directory or file) if (!QString::fromUtf8(filepath.GetChars()).endsWith(QLatin1Char('/'))) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: regular file detected"; object = new PLT_MediaItem(); // Set the title using the filename for now QString uri = QString::fromUtf8(filepath.GetChars()); int index = uri.indexOf(QLatin1String("/?file:")) + 7; QString path = uri.remove(0, index); QString title = path.section(QLatin1Char('/'), -1); if (!keep_extension_in_title) { title = title.section(QLatin1Char('.'), -2); } object->m_Title = NPT_String(title.toUtf8().data()); if (DRawDecoder::isRawFile(QUrl::fromLocalFile(QString::fromUtf8(filepath.GetChars())))) { // Special case for RAW file where extension need to be patched as JPEG for client renderer, // as we provide a JPEG preview. object->m_Title += ".jpg"; } if (object->m_Title.GetLength() == 0) { goto failure; } // make sure we return something with a valid mimetype if (d->filterUnknownOut && NPT_StringsEqual(PLT_MimeType::GetMimeType(filepath, &context), "application/octet-stream")) { goto failure; } qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: Create item as MediaItem \"" << object->m_Title.GetChars() << "\""; // Set the protocol Info from the extension resource.m_ProtocolInfo = PLT_ProtocolInfo::GetProtocolInfo(filepath, true, &context); if (!resource.m_ProtocolInfo.IsValid()) { goto failure; } // format the resource URI NPT_String url = filepath.SubString(filepath.Find("/?file:") + 7); qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: Item URI:\"" << url.GetChars() << "\""; // Set the resource file size NPT_FileInfo info; NPT_CHECK_LABEL_FATAL(NPT_File::GetInfo(url, &info), failure); resource.m_Size = info.m_Size; // get list of ip addresses NPT_List ips; NPT_CHECK_LABEL_SEVERE(PLT_UPnPMessageHelper::GetIPAddresses(ips), failure); // if we're passed an interface where we received the request from // move the ip to the top so that it is used for the first resource if (context.GetLocalAddress().GetIpAddress().ToString() != "0.0.0.0") { ips.Remove(context.GetLocalAddress().GetIpAddress()); ips.Insert(ips.GetFirstItem(), context.GetLocalAddress().GetIpAddress()); } else if (!allip) { NPT_LOG_WARNING("Could not determine local interface IP so we might return an unreachable IP"); } object->m_ObjectClass.type = PLT_MediaItem::GetUPnPClass(filepath, &context); // add as many resources as we have interfaces NPT_HttpUrl base_uri("127.0.0.1", context.GetLocalAddress().GetPort(), NPT_HttpUrl::PercentEncode(d->urlRoot, NPT_Uri::PathCharsToEncode)); NPT_List::Iterator ip = ips.GetFirstItem(); while (ip) { resource.m_Uri = BuildResourceUri(base_uri, ip->ToString(), url); object->m_Resources.Add(resource); ++ip; // if we only want the one resource reachable by client if (!allip) { break; } } } else { qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: directory detected"; object = new PLT_MediaContainer; // Assign a title for this container if (filepath.Compare("/", true) == 0) { object->m_Title = "Root"; } else { QString path = QString::fromUtf8(filepath.GetChars()); QString title = path.section(QLatin1Char('/'), -2, -2); // We drop extra '/' too on the front of name object->m_Title = NPT_String(title.toUtf8().data()); if (object->m_Title.GetLength() == 0) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: MediaContainer item name is empty."; goto failure; } } // Get the number of children for this container NPT_LargeSize count = 0; if (with_count && NPT_SUCCEEDED(NPT_File::GetSize(filepath, count))) { ((PLT_MediaContainer*)object)->m_ChildrenCount = (NPT_Int32)count; } object->m_ObjectClass.type = "object.container.storageFolder"; qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: Create item as MediaContainer \"" << object->m_Title.GetChars() << "\""; } // is it the root? if (filepath.Compare("/", true) == 0) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: New item is root"; object->m_ParentID = "-1"; // krazy:exclude=doublequote_chars object->m_ObjectID = "0"; // krazy:exclude=doublequote_chars } else { // is the parent path the root? if (filepath.EndsWith("/")) { object->m_ParentID = "0"; // krazy:exclude=doublequote_chars } else { object->m_ParentID = "0" + filepath.Left(filepath.Find("/?file:") + 1); // krazy:exclude=doublequote_chars } object->m_ObjectID = "0" + filepath.SubString(0); // krazy:exclude=doublequote_chars } qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: New item parent ID:" << object->m_ParentID.GetChars(); qCDebug(DIGIKAM_MEDIASRV_LOG) << "BuildFromFilePath() :: New item object ID:" << object->m_ObjectID.GetChars(); return object; failure: qCDebug(DIGIKAM_MEDIASRV_LOG) << "Failed to build didl for file \"" << filepath.GetChars() << "\""; delete object; return nullptr; } NPT_Result DLNAMediaServerDelegate::GetFilePath(const char* object_id, NPT_String& filepath) { if (!object_id) { return NPT_ERROR_INVALID_PARAMETERS; } filepath = "/"; // krazy:exclude=doublequote_chars // object id is formatted as 0/ if (NPT_StringLength(object_id) >= 1) { int index = 0; if (object_id[0] == '0' && object_id[1] == '/') { index = 2; } else if (object_id[0] == '0') { index = 1; } filepath += (object_id + index); } qCDebug(DIGIKAM_MEDIASRV_LOG) << "GetFilePath() :: Object id:" << object_id << "filepath:" << filepath.GetChars(); return NPT_SUCCESS; } NPT_Result DLNAMediaServerDelegate::OnSearchContainer(PLT_ActionReference& action, const char* object_id, const char* search_criteria, const char* /* filter */, NPT_UInt32 /* starting_index */, NPT_UInt32 /* requested_count */, const char* /* sort_criteria */, const PLT_HttpRequestContext& /* context */) { // parse search criteria // TODO: HACK TO PASS DLNA qCDebug(DIGIKAM_MEDIASRV_LOG) << "Received Search request for object \"" << object_id << "\" with search \"" << search_criteria << "\""; if (search_criteria && NPT_StringsEqual(search_criteria, "Unknownfieldname")) { // error qCDebug(DIGIKAM_MEDIASRV_LOG) << "Unsupported or invalid search criteria" << search_criteria; action->SetError(708, "Unsupported or invalid search criteria"); return NPT_FAILURE; } // locate the file from the object ID NPT_String dir; if (NPT_FAILED(GetFilePath(object_id, dir))) { // error qCDebug(DIGIKAM_MEDIASRV_LOG) << "ObjectID not found" << object_id; action->SetError(710, "No Such Container."); return NPT_FAILURE; } qCDebug(DIGIKAM_MEDIASRV_LOG) << "OnSearchContainer() :: dir =" << dir.GetChars(); // retrieve the item type NPT_FileInfo info; NPT_Result res = NPT_File::GetInfo(dir, &info); if (NPT_FAILED(res) || (info.m_Type != NPT_FileInfo::FILE_TYPE_DIRECTORY)) { // error qCDebug(DIGIKAM_MEDIASRV_LOG) << "No such container" << dir.GetChars(); action->SetError(710, "No such container"); return NPT_FAILURE; } return NPT_ERROR_NOT_IMPLEMENTED; } NPT_String DLNAMediaServerDelegate::BuildSafeResourceUri(const NPT_HttpUrl& base_uri, const char* host, const char* file_path) { NPT_String result; NPT_HttpUrl uri = base_uri; if (host) { uri.SetHost(host); } NPT_String uri_path = uri.GetPath(); if (!uri_path.EndsWith("/")) { uri_path += "/"; // krazy:exclude=doublequote_chars } // some controllers (like WMP) will call us with an already urldecoded version. // We're intentionally prepending a known urlencoded string // to detect it when we receive the request urlencoded or already decoded to avoid double decoding uri_path += "%/"; uri_path += file_path; // set path uri.SetPath(uri_path); // 360 hack: force inclusion of port in case it's 80 return uri.ToStringWithDefaultPort(0); } NPT_Result DLNAMediaServerDelegate::ExtractResourcePath(const NPT_HttpUrl& url, NPT_String& file_path) { // Extract non decoded path, we need to autodetect urlencoding NPT_String uri_path = url.GetPath(); NPT_String url_root_encode = NPT_Uri::PercentEncode(d->urlRoot, NPT_Uri::PathCharsToEncode); NPT_Ordinal skip = 0; if (uri_path.StartsWith(d->urlRoot)) { skip = d->urlRoot.GetLength(); } else if (uri_path.StartsWith(url_root_encode)) { skip = url_root_encode.GetLength(); } else { return NPT_FAILURE; } // account for extra slash skip += ((d->urlRoot == "/") ? 0 : 1); file_path = uri_path.SubString(skip); // detect if client such as WMP sent a non urlencoded url if (file_path.StartsWith("%/")) { NPT_LOG_FINE("Received a urldecoded version of our url!"); file_path.Erase(0, 2); } else { // remove our prepended string we used to detect urldecoded version if (file_path.StartsWith("%25/")) { file_path.Erase(0, 4); } // ok to urldecode file_path = NPT_Uri::PercentDecode(file_path); } return NPT_SUCCESS; } NPT_Result DLNAMediaServerDelegate::ServeFile(const NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response, const NPT_String& file_path) { // Try to stream image file as transcoded preview. // This will serve image in reduced size, including all know image formats // supported by digiKam core, as JPEG, PNG, TIFF, and RAW files for ex. DImg dimg = PreviewLoadThread::loadFastSynchronously(QString::fromUtf8(file_path.GetChars()), 2048); if (dimg.isNull()) { // Not a supported image format. Try to stream file as well, without transcoding. // TODO : support video file as transcoded video stream using QtAV (if possible). qCDebug(DIGIKAM_MEDIASRV_LOG) << file_path.GetChars() << "not recognized as an image to stream as preview."; NPT_CHECK_WARNING(PLT_HttpServer::ServeFile(request, context, response, file_path)); return NPT_SUCCESS; } // This code is basically the same than PLT_HttpServer::ServeFile() excepted the // image trancoding pass served as byte stream. NPT_InputStreamReference stream; NPT_File file(file_path); NPT_FileInfo file_info; // prevent hackers from accessing files outside of our root if ((file_path.Find("/..") >= 0) || (file_path.Find("\\..") >= 0) || NPT_FAILED(NPT_File::GetInfo(file_path, &file_info))) { return NPT_ERROR_NO_SUCH_ITEM; } // check for range requests const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE); // handle potential 304 only if range header not set NPT_DateTime date; NPT_TimeStamp timestamp; if (NPT_SUCCEEDED(PLT_UPnPMessageHelper::GetIfModifiedSince((NPT_HttpMessage&)request, date)) && !range_spec) { date.ToTimeStamp(timestamp); NPT_LOG_INFO_5("File %s timestamps: request=%d (%s) vs file=%d (%s)", (const char*)request.GetUrl().GetPath(), (NPT_UInt32)timestamp.ToSeconds(), (const char*)date.ToString(), (NPT_UInt32)file_info.m_ModificationTime, (const char*)NPT_DateTime(file_info.m_ModificationTime).ToString()); if (timestamp >= file_info.m_ModificationTime) { // it's a match NPT_LOG_FINE_1("Returning 304 for %s", request.GetUrl().GetPath().GetChars()); response.SetStatus(304, "Not Modified", NPT_HTTP_PROTOCOL_1_1); return NPT_SUCCESS; } } // Trancoding image as preview. QImage preview = dimg.copyQImage(); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); preview.save(&buffer, "JPG"); buffer.close(); stream = new NPT_MemoryStream(ba.data(), (NPT_Size)ba.size()); if (stream.IsNull()) { return NPT_ERROR_NO_SUCH_ITEM; } // set Last-Modified and Cache-Control headers if (file_info.m_ModificationTime) { NPT_DateTime last_modified = NPT_DateTime(file_info.m_ModificationTime); response.GetHeaders().SetHeader("Last-Modified", last_modified.ToString(NPT_DateTime::FORMAT_RFC_1123), true); response.GetHeaders().SetHeader("Cache-Control", "max-age=0,must-revalidate", true); } PLT_HttpRequestContext tmp_context(request, context); NPT_CHECK_WARNING(PLT_HttpServer::ServeStream(request, context, response, stream, "image/jpeg")); return NPT_SUCCESS; } NPT_String DLNAMediaServerDelegate::BuildResourceUri(const NPT_HttpUrl& base_uri, const char* host, const char* file_path) { return BuildSafeResourceUri(base_uri, host, file_path); } NPT_Result DLNAMediaServerDelegate::OnUpdateObject(PLT_ActionReference&, const char*, NPT_Map&, NPT_Map&, const PLT_HttpRequestContext&) { return NPT_SUCCESS; } bool DLNAMediaServerDelegate::ProcessFile(const NPT_String&, const char*) { return true; } } // namespace Digikam diff --git a/core/dplugins/generic/tools/mediaserver/server/dmediaservermngr.cpp b/core/dplugins/generic/tools/mediaserver/server/dmediaservermngr.cpp index eada5f7acb..db806ae636 100644 --- a/core/dplugins/generic/tools/mediaserver/server/dmediaservermngr.cpp +++ b/core/dplugins/generic/tools/mediaserver/server/dmediaservermngr.cpp @@ -1,363 +1,363 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2012-05-28 * Description : Media server manager * * Copyright (C) 2012 by Smit Mehta * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "dmediaservermngr.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dnotificationwrapper.h" namespace Digikam { class Q_DECL_HIDDEN DMediaServerMngrCreator { public: DMediaServerMngr object; }; Q_GLOBAL_STATIC(DMediaServerMngrCreator, creator) // --------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DMediaServerMngr::Private { public: explicit Private() { server = nullptr; } // Configuration XML file to store albums map to share in case of restoring between sessions. QString mapsConf; // Server instance pointer. DMediaServer* server; // The current albums collection to share. MediaServerMap collectionMap; static const QString configGroupName; static const QString configStartServerOnStartupEntry; }; const QString DMediaServerMngr::Private::configGroupName(QLatin1String("DLNA Settings")); const QString DMediaServerMngr::Private::configStartServerOnStartupEntry(QLatin1String("Start MediaServer At Startup")); DMediaServerMngr* DMediaServerMngr::instance() { return &creator->object; } DMediaServerMngr::DMediaServerMngr() : d(new Private) { d->mapsConf = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/mediaserver.xml"); } DMediaServerMngr::~DMediaServerMngr() { delete d; } QString DMediaServerMngr::configGroupName() const { return d->configGroupName; } QString DMediaServerMngr::configStartServerOnStartupEntry() const { return d->configStartServerOnStartupEntry; } void DMediaServerMngr::cleanUp() { delete d->server; d->server = nullptr; } bool DMediaServerMngr::loadAtStartup() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup dlnaConfigGroup = config->group(configGroupName()); bool startServerOnStartup = dlnaConfigGroup.readEntry(configStartServerOnStartupEntry(), false); bool result = true; if (startServerOnStartup) { // Restore the old sharing configuration and start the server. result &= load(); result &= startMediaServer(); mediaServerNotification(result); return result; } return false; } void DMediaServerMngr::saveAtShutdown() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup dlnaConfigGroup = config->group(configGroupName()); bool startServerOnStartup = dlnaConfigGroup.readEntry(configStartServerOnStartupEntry(), false); if (startServerOnStartup) { // Save the current sharing configuration for the next session. save(); } cleanUp(); } void DMediaServerMngr::mediaServerNotification(bool started) { DNotificationWrapper(QLatin1String("mediaserverloadstartup"), started ? i18n("Media Server have been started") : i18n("Media Server cannot be started!"), qApp->activeWindow(), qApp->applicationName()); } void DMediaServerMngr::setItemsList(const QString& aname, const QList& urls) { d->collectionMap.clear(); d->collectionMap.insert(aname, urls); } QList DMediaServerMngr::itemsList() const { QList ret; if (!d->collectionMap.isEmpty()) { QList > ulst = d->collectionMap.values(); - foreach(QList urls, ulst) + foreach (QList urls, ulst) { ret << urls; } } return ret; } void DMediaServerMngr::setCollectionMap(const MediaServerMap& map) { d->collectionMap = map; } MediaServerMap DMediaServerMngr::collectionMap() const { return d->collectionMap; } bool DMediaServerMngr::startMediaServer() { if (!d->server) { d->server = new DMediaServer(); if (!d->server->init()) { cleanUp(); return false; } } if (d->collectionMap.isEmpty()) { cleanUp(); return false; } d->server->addAlbumsOnServer(d->collectionMap); return true; } bool DMediaServerMngr::isRunning() const { return d->server ? true : false; } int DMediaServerMngr::albumsShared() const { if (d->collectionMap.isEmpty()) return 0; return d->collectionMap.uniqueKeys().count(); } int DMediaServerMngr::itemsShared() const { return itemsList().count(); } bool DMediaServerMngr::save() { QDomDocument doc(QLatin1String("mediaserverlist")); doc.setContent(QString::fromUtf8("")); QDomElement docElem = doc.documentElement(); auto end = d->collectionMap.cend(); for (auto it = d->collectionMap.cbegin() ; it != end ; ++it) { QDomElement elm = doc.createElement(QLatin1String("album")); elm.setAttribute(QLatin1String("title"), it.key()); // ---------------------- QDomElement data; - foreach(const QUrl& url, it.value()) + foreach (const QUrl& url, it.value()) { data = doc.createElement(QLatin1String("path")); data.setAttribute(QLatin1String("value"), url.toLocalFile()); elm.appendChild(data); } docElem.appendChild(elm); } QFile file(d->mapsConf); if (!file.open(QIODevice::WriteOnly)) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot open XML file to store MediaServer list"; qCDebug(DIGIKAM_MEDIASRV_LOG) << file.fileName(); return false; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); stream.setAutoDetectUnicode(true); stream << doc.toString(4); file.close(); return true; } bool DMediaServerMngr::load() { QFile file(d->mapsConf); if (file.exists()) { if (!file.open(QIODevice::ReadOnly)) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot open XML file to load MediaServer list"; return false; } QDomDocument doc(QLatin1String("mediaserverlist")); if (!doc.setContent(&file)) { qCDebug(DIGIKAM_MEDIASRV_LOG) << "Cannot load MediaServer list XML file"; file.close(); return false; } QDomElement docElem = doc.documentElement(); MediaServerMap map; QList urls; QString album; for (QDomNode n = docElem.firstChild() ; !n.isNull() ; n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) { continue; } if (e.tagName() != QLatin1String("album")) { continue; } album = e.attribute(QLatin1String("title")); urls.clear(); for (QDomNode n2 = e.firstChild() ; !n2.isNull() ; n2 = n2.nextSibling()) { QDomElement e2 = n2.toElement(); if (e2.isNull()) { continue; } QString name2 = e2.tagName(); QString val2 = e2.attribute(QLatin1String("value")); if (name2 == QLatin1String("path")) { urls << QUrl::fromLocalFile(val2); } } map.insert(album, urls); } setCollectionMap(map); file.close(); return true; } else { return false; } } } // namespace Digikam diff --git a/core/dplugins/generic/tools/mediaserver/tests/dmediaserver_test.cpp b/core/dplugins/generic/tools/mediaserver/tests/dmediaserver_test.cpp index 6317ccab66..c025f37afd 100644 --- a/core/dplugins/generic/tools/mediaserver/tests/dmediaserver_test.cpp +++ b/core/dplugins/generic/tools/mediaserver/tests/dmediaserver_test.cpp @@ -1,101 +1,101 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-12-28 * Description : stand alone test for DMediaServer * * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "dmediaservermngr.h" #include "dfiledialog.h" using namespace Digikam; int main(int argc, char* argv[]) { QApplication app(argc, argv); QList list; MediaServerMap map; QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); if (argc <= 1) { QStringList files = DFileDialog::getOpenFileNames(nullptr, QString::fromLatin1("Select Files to Share With Media Server"), QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first(), QLatin1String("Image Files (*.png *.jpg *.tif *.bmp *.gif)")); - foreach(const QString& f, files) + foreach (const QString& f, files) { list.append(QUrl::fromLocalFile(f)); } } else { for (int i = 1 ; i < argc ; i++) { list.append(QUrl::fromLocalFile(QString::fromLocal8Bit(argv[i]))); } } if (!list.isEmpty()) { map.insert(QLatin1String("Test Collection"), list); DMediaServerMngr::instance()->setCollectionMap(map); } else { if (!DMediaServerMngr::instance()->load()) return -1; } if (DMediaServerMngr::instance()->startMediaServer()) { QProgressDialog* const pdlg = new QProgressDialog(nullptr); pdlg->setLabelText(QLatin1String("Sharing files on the network")); pdlg->setMinimumDuration(0); pdlg->setCancelButtonText(QLatin1String("Close")); pdlg->setMaximum(0); pdlg->setMinimum(0); pdlg->setValue(0); pdlg->exec(); } else { qDebug() << "Failed to start the Media Server..."; } DMediaServerMngr::instance()->save(); DMediaServerMngr::instance()->cleanUp(); return 0; } diff --git a/core/dplugins/generic/tools/panorama/manager/cpfindbinary.cpp b/core/dplugins/generic/tools/panorama/manager/cpfindbinary.cpp index 6481b1ea37..718d8167fa 100644 --- a/core/dplugins/generic/tools/panorama/manager/cpfindbinary.cpp +++ b/core/dplugins/generic/tools/panorama/manager/cpfindbinary.cpp @@ -1,60 +1,60 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-05-23 * Description : Autodetects cpfind binary program and version * * Copyright (C) 2011-2015 by Benjamin Girault * * 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, 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. * * ============================================================ */ #include "cpfindbinary.h" // Local includes #include "digikam_debug.h" namespace DigikamGenericPanoramaPlugin { bool CPFindBinary::parseHeader(const QString& output) { QStringList lines = output.split(QLatin1Char('\n')); m_developmentVersion = false; - foreach(const QString& line, lines) + foreach (const QString& line, lines) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << path() << " help header line: \n" << line; if (line.contains(headerRegExp)) { m_version = headerRegExp.cap(2); if (!headerRegExp.cap(1).isEmpty()) { m_developmentVersion = true; } return true; } m_developmentVersion = true; } return false; } } // namespace DigikamGenericPanoramaPlugin diff --git a/core/dplugins/generic/tools/printcreator/wizard/advprintalbumspage.cpp b/core/dplugins/generic/tools/printcreator/wizard/advprintalbumspage.cpp index d9b96dc32b..f40e2d5d65 100644 --- a/core/dplugins/generic/tools/printcreator/wizard/advprintalbumspage.cpp +++ b/core/dplugins/generic/tools/printcreator/wizard/advprintalbumspage.cpp @@ -1,112 +1,112 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-27 * Description : a tool to print images * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "advprintalbumspage.h" // Qt includes #include // Local includes #include "advprintwizard.h" namespace DigikamGenericPrintCreatorPlugin { class Q_DECL_HIDDEN AdvPrintAlbumsPage::Private { public: explicit Private(QWizard* const dialog) : albumSupport(false), albumSelector(nullptr), wizard(nullptr), iface(nullptr) { wizard = dynamic_cast(dialog); if (wizard) { iface = wizard->iface(); } } bool albumSupport; QWidget* albumSelector; AdvPrintWizard* wizard; DInfoInterface* iface; }; AdvPrintAlbumsPage::AdvPrintAlbumsPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private(dialog)) { if (d->iface) { d->albumSelector = d->iface->albumChooser(this); connect(d->iface, SIGNAL(signalAlbumChooserSelectionChanged()), this, SIGNAL(completeChanged())); } else { d->albumSelector = new QWidget(this); } setPageWidget(d->albumSelector); setLeftBottomPix(QIcon::fromTheme(QLatin1String("folder-mail"))); } AdvPrintAlbumsPage::~AdvPrintAlbumsPage() { delete d; } bool AdvPrintAlbumsPage::validatePage() { if (!d->iface) return false; if (d->iface->albumChooserItems().isEmpty()) return false; d->wizard->settings()->inputImages.clear(); // update image list with album contents. - foreach(const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) + foreach (const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) { d->wizard->settings()->inputImages << url; } return true; } bool AdvPrintAlbumsPage::isComplete() const { if (!d->iface) return false; return (!d->iface->albumChooserItems().isEmpty()); } } // namespace DigikamGenericPrintCreatorPlugin diff --git a/core/dplugins/generic/tools/printcreator/wizard/advprintcaptionpage.cpp b/core/dplugins/generic/tools/printcreator/wizard/advprintcaptionpage.cpp index 08acf3505a..5b0bc258ee 100644 --- a/core/dplugins/generic/tools/printcreator/wizard/advprintcaptionpage.cpp +++ b/core/dplugins/generic/tools/printcreator/wizard/advprintcaptionpage.cpp @@ -1,375 +1,375 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-25 * Description : a tool to print images * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "advprintcaptionpage.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dmetadata.h" #include "advprintwizard.h" #include "advprintphoto.h" namespace DigikamGenericPrintCreatorPlugin { class Q_DECL_HIDDEN AdvPrintCaptionPage::Private { public: template class Q_DECL_HIDDEN WizardUI : public QWidget, public Ui_Class { public: explicit WizardUI(QWidget* const parent) : QWidget(parent) { this->setupUi(this); } }; typedef WizardUI CaptionUI; public: explicit Private(QWizard* const dialog) : settings(nullptr), iface(nullptr) { captionUi = new CaptionUI(dialog); wizard = dynamic_cast(dialog); if (wizard) { settings = wizard->settings(); iface = wizard->iface(); } } CaptionUI* captionUi; AdvPrintWizard* wizard; AdvPrintSettings* settings; DInfoInterface* iface; }; AdvPrintCaptionPage::AdvPrintCaptionPage(QWizard* const wizard, const QString& title) : DWizardPage(wizard, title), d(new Private(wizard)) { QMap map = AdvPrintSettings::captionTypeNames(); QMap::const_iterator it = map.constBegin(); while (it != map.constEnd()) { d->captionUi->m_captionType->addItem(it.value(), (int)it.key()); ++it; } // ---------------------------------------------------------------------- connect(d->captionUi->m_captionType, SIGNAL(activated(int)), this, SLOT(slotCaptionChanged(int))); connect(d->captionUi->m_FreeCaptionFormat, SIGNAL(editingFinished()), this, SLOT(slotUpdateCaptions())); connect(d->captionUi->m_font_name, SIGNAL(currentFontChanged(QFont)), this, SLOT(slotUpdateCaptions())); connect(d->captionUi->m_font_size, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateCaptions())); connect(d->captionUi->m_font_color, SIGNAL(signalColorSelected(QColor)), this, SLOT(slotUpdateCaptions())); connect(d->captionUi->mPrintList, SIGNAL(signalImageListChanged()), this, SLOT(slotUpdateCaptions())); // ----------------------------------- d->captionUi->mPrintList->setIface(d->iface); d->captionUi->mPrintList->setAllowDuplicate(true); d->captionUi->mPrintList->setControlButtonsPlacement(DItemsList::NoControlButtons); d->captionUi->mPrintList->listView()->setColumn(DItemsListView::User1, i18nc("@title:column", "Caption"), true); // ----------------------------------- setPageWidget(d->captionUi); setLeftBottomPix(QIcon::fromTheme(QLatin1String("imagecomment"))); } AdvPrintCaptionPage::~AdvPrintCaptionPage() { delete d; } DItemsList* AdvPrintCaptionPage::imagesList() const { return d->captionUi->mPrintList; } void AdvPrintCaptionPage::initializePage() { d->captionUi->m_captionType->setCurrentIndex(d->settings->captionType); enableCaptionGroup(d->captionUi->m_captionType->currentIndex()); d->captionUi->m_font_color->setColor(d->settings->captionColor); d->captionUi->m_font_name->setCurrentFont(d->settings->captionFont.family()); d->captionUi->m_font_size->setValue(d->settings->captionSize); d->captionUi->m_FreeCaptionFormat->setText(d->settings->captionTxt); slotCaptionChanged(d->captionUi->m_captionType->currentIndex()); slotUpdateImagesList(); slotUpdateCaptions(); } bool AdvPrintCaptionPage::validatePage() { d->settings->captionType = (AdvPrintSettings::CaptionType)d->captionUi->m_captionType->currentIndex(); d->settings->captionColor = d->captionUi->m_font_color->color(); d->settings->captionFont = QFont(d->captionUi->m_font_name->currentFont()); d->settings->captionSize = d->captionUi->m_font_size->value(); d->settings->captionTxt = d->captionUi->m_FreeCaptionFormat->text(); return true; } void AdvPrintCaptionPage::slotUpdateImagesList() { d->captionUi->mPrintList->listView()->clear(); d->captionUi->mPrintList->slotAddImages(d->wizard->itemsList()); } void AdvPrintCaptionPage::blockCaptionButtons(bool block) { d->captionUi->m_captionType->blockSignals(block); d->captionUi->m_free_label->blockSignals(block); d->captionUi->m_font_name->blockSignals(block); d->captionUi->m_font_size->blockSignals(block); d->captionUi->m_font_color->blockSignals(block); } void AdvPrintCaptionPage::enableCaptionGroup(int index) { bool fontSettingsEnabled; if (index == AdvPrintSettings::NONE) { fontSettingsEnabled = false; d->captionUi->m_customCaptionGB->setEnabled(false); } else if (index == AdvPrintSettings::CUSTOM) { fontSettingsEnabled = true; d->captionUi->m_customCaptionGB->setEnabled(true); } else { fontSettingsEnabled = true; d->captionUi->m_customCaptionGB->setEnabled(false); } d->captionUi->m_font_name->setEnabled(fontSettingsEnabled); d->captionUi->m_font_size->setEnabled(fontSettingsEnabled); d->captionUi->m_font_color->setEnabled(fontSettingsEnabled); } void AdvPrintCaptionPage::slotCaptionChanged(int index) { enableCaptionGroup(index); slotUpdateCaptions(); } void AdvPrintCaptionPage::updateCaption(AdvPrintPhoto* const pPhoto) { if (pPhoto) { if (!pPhoto->m_pAdvPrintCaptionInfo && d->captionUi->m_captionType->currentIndex() != AdvPrintSettings::NONE) { pPhoto->m_pAdvPrintCaptionInfo = new AdvPrintCaptionInfo(); } else if (pPhoto->m_pAdvPrintCaptionInfo && d->captionUi->m_captionType->currentIndex() == AdvPrintSettings::NONE) { delete pPhoto->m_pAdvPrintCaptionInfo; pPhoto->m_pAdvPrintCaptionInfo = nullptr; } if (pPhoto->m_pAdvPrintCaptionInfo) { pPhoto->m_pAdvPrintCaptionInfo->m_captionColor = d->captionUi->m_font_color->color(); pPhoto->m_pAdvPrintCaptionInfo->m_captionSize = d->captionUi->m_font_size->value(); pPhoto->m_pAdvPrintCaptionInfo->m_captionFont = d->captionUi->m_font_name->currentFont(); pPhoto->m_pAdvPrintCaptionInfo->m_captionType = (AdvPrintSettings::CaptionType)d->captionUi->m_captionType->currentIndex(); pPhoto->m_pAdvPrintCaptionInfo->m_captionText = d->captionUi->m_FreeCaptionFormat->text(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Update caption properties for" << pPhoto->m_url; } } } void AdvPrintCaptionPage::slotUpdateCaptions() { if (d->settings->photos.size()) { - foreach(AdvPrintPhoto* const pPhoto, d->settings->photos) + foreach (AdvPrintPhoto* const pPhoto, d->settings->photos) { updateCaption(pPhoto); if (pPhoto && pPhoto->m_pAdvPrintCaptionInfo) { DItemsListViewItem* const lvItem = d->captionUi->mPrintList->listView()->findItem(pPhoto->m_url); if (lvItem) { QString cap; if (pPhoto->m_pAdvPrintCaptionInfo->m_captionType != AdvPrintSettings::NONE) cap = captionFormatter(pPhoto); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << cap; lvItem->setText(DItemsListView::User1, cap); } } } } // create our photo sizes list d->wizard->previewPhotos(); } QString AdvPrintCaptionPage::captionFormatter(AdvPrintPhoto* const photo) { if (!photo->m_pAdvPrintCaptionInfo) { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Internal caption info container is NULL for" << photo->m_url; return QString(); } QString resolution; QSize imageSize; QString format; // %f filename // %c comment // %d date-time // %t exposure time // %i iso // %r resolution // %a aperture // %l focal length switch (photo->m_pAdvPrintCaptionInfo->m_captionType) { case AdvPrintSettings::FILENAME: format = QLatin1String("%f"); break; case AdvPrintSettings::DATETIME: format = QLatin1String("%d"); break; case AdvPrintSettings::COMMENT: format = QLatin1String("%c"); break; case AdvPrintSettings::CUSTOM: format = photo->m_pAdvPrintCaptionInfo->m_captionText; break; default: qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "UNKNOWN caption type " << photo->m_pAdvPrintCaptionInfo->m_captionType; break; } format.replace(QLatin1String("\\n"), QLatin1String("\n")); if (photo->m_iface) { DItemInfo info(photo->m_iface->itemInfo(photo->m_url)); imageSize = info.dimensions(); format.replace(QString::fromUtf8("%c"), info.comment()); format.replace(QString::fromUtf8("%d"), QLocale().toString(info.dateTime(), QLocale::ShortFormat)); format.replace(QString::fromUtf8("%f"), info.name()); format.replace(QString::fromUtf8("%t"), info.exposureTime()); format.replace(QString::fromUtf8("%i"), info.sensitivity()); format.replace(QString::fromUtf8("%a"), info.aperture()); format.replace(QString::fromUtf8("%l"), info.focalLength()); } else { QFileInfo fi(photo->m_url.toLocalFile()); DMetadata meta(photo->m_url.toLocalFile()); imageSize = meta.getItemDimensions(); format.replace(QString::fromUtf8("%c"), meta.getItemComments()[QLatin1String("x-default")].caption); format.replace(QString::fromUtf8("%d"), QLocale().toString(meta.getItemDateTime(), QLocale::ShortFormat)); format.replace(QString::fromUtf8("%f"), fi.fileName()); PhotoInfoContainer photoInfo = meta.getPhotographInformation(); format.replace(QString::fromUtf8("%t"), photoInfo.exposureTime); format.replace(QString::fromUtf8("%i"), photoInfo.sensitivity); format.replace(QString::fromUtf8("%a"), photoInfo.aperture); format.replace(QString::fromUtf8("%l"), photoInfo.focalLength); } if (imageSize.isValid()) { resolution = QString::fromUtf8("%1x%2").arg(imageSize.width()).arg(imageSize.height()); } format.replace(QString::fromUtf8("%r"), resolution); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Caption for" << photo->m_url << ":" << format; return format; } } // namespace DigikamGenericPrintCreatorPlugin diff --git a/core/dplugins/generic/tools/printcreator/wizard/advprintphotopage.cpp b/core/dplugins/generic/tools/printcreator/wizard/advprintphotopage.cpp index a349f6dc77..bd7e869363 100644 --- a/core/dplugins/generic/tools/printcreator/wizard/advprintphotopage.cpp +++ b/core/dplugins/generic/tools/printcreator/wizard/advprintphotopage.cpp @@ -1,1545 +1,1545 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-25 * Description : a tool to print images * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "advprintphotopage.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "advprintwizard.h" #include "advprintcustomdlg.h" #include "templateicon.h" namespace DigikamGenericPrintCreatorPlugin { static const char* const CUSTOM_PAGE_LAYOUT_NAME = I18N_NOOP("Custom"); class Q_DECL_HIDDEN AdvPrintPhotoPage::Private { public: template class Q_DECL_HIDDEN WizardUI : public QWidget, public Ui_Class { public: explicit WizardUI(QWidget* const parent) : QWidget(parent) { this->setupUi(this); } }; typedef WizardUI PhotoUI; public: explicit Private(QWizard* const dialog) : pageSetupDlg(nullptr), printer(nullptr), wizard(nullptr), settings(nullptr), iface(nullptr) { photoUi = new PhotoUI(dialog); wizard = dynamic_cast(dialog); if (wizard) { settings = wizard->settings(); iface = wizard->iface(); } } PhotoUI* photoUi; QPageSetupDialog* pageSetupDlg; QPrinter* printer; QList printerList; AdvPrintWizard* wizard; AdvPrintSettings* settings; DInfoInterface* iface; }; AdvPrintPhotoPage::AdvPrintPhotoPage(QWizard* const wizard, const QString& title) : DWizardPage(wizard, title), d(new Private(wizard)) { d->photoUi->BtnPreviewPageUp->setIcon(QIcon::fromTheme(QLatin1String("go-next")) .pixmap(16, 16)); d->photoUi->BtnPreviewPageDown->setIcon(QIcon::fromTheme(QLatin1String("go-previous")) .pixmap(16, 16)); // ---------------------- d->photoUi->m_printer_choice->setEditable(false); d->photoUi->m_printer_choice->setWhatsThis(i18n("Select your preferred print output.")); // Populate hardcoded printers QMap map2 = AdvPrintSettings::outputNames(); QMap::const_iterator it2 = map2.constBegin(); while (it2 != map2.constEnd()) { d->photoUi->m_printer_choice->addSqueezedItem(it2.value(), (int)it2.key()); ++it2; } // Populate real printers d->printerList = QPrinterInfo::availablePrinters(); for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { d->photoUi->m_printer_choice->addSqueezedItem(it->printerName()); } connect(d->photoUi->m_printer_choice, SIGNAL(activated(QString)), this, SLOT(slotOutputChanged(QString))); connect(d->photoUi->BtnPreviewPageUp, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageUpClicked())); connect(d->photoUi->BtnPreviewPageDown, SIGNAL(clicked()), this, SLOT(slotBtnPreviewPageDownClicked())); connect(d->photoUi->ListPhotoSizes, SIGNAL(currentRowChanged(int)), this, SLOT(slotListPhotoSizesSelected())); connect(d->photoUi->m_pagesetup, SIGNAL(clicked()), this, SLOT(slotPageSetup())); if (d->photoUi->mPrintList->layout()) { delete d->photoUi->mPrintList->layout(); } // ----------------------------------- d->photoUi->mPrintList->setIface(d->iface); d->photoUi->mPrintList->setAllowDuplicate(true); d->photoUi->mPrintList->setControlButtons(DItemsList::Add | DItemsList::Remove | DItemsList::MoveUp | DItemsList::MoveDown | DItemsList::Clear | DItemsList::Save | DItemsList::Load); d->photoUi->mPrintList->setControlButtonsPlacement(DItemsList::ControlButtonsAbove); d->photoUi->mPrintList->enableDragAndDrop(false); d->photoUi->BmpFirstPagePreview->setAlignment(Qt::AlignHCenter); connect(d->photoUi->mPrintList, SIGNAL(signalMoveDownItem()), this, SLOT(slotBtnPrintOrderDownClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalMoveUpItem()), this, SLOT(slotBtnPrintOrderUpClicked())); connect(d->photoUi->mPrintList, SIGNAL(signalAddItems(QList)), this, SLOT(slotAddItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalRemovedItems(QList)), this, SLOT(slotRemovingItems(QList))); connect(d->photoUi->mPrintList, SIGNAL(signalContextMenuRequested()), this, SLOT(slotContextMenuRequested())); connect(d->photoUi->mPrintList, SIGNAL(signalXMLSaveItem(QXmlStreamWriter&,int)), this, SLOT(slotXMLSaveItem(QXmlStreamWriter&,int))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamWriter&)), this, SLOT(slotXMLCustomElement(QXmlStreamWriter&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLLoadImageElement(QXmlStreamReader&)), this, SLOT(slotXMLLoadElement(QXmlStreamReader&))); connect(d->photoUi->mPrintList, SIGNAL(signalXMLCustomElements(QXmlStreamReader&)), this, SLOT(slotXMLCustomElement(QXmlStreamReader&))); // ----------------------------------- setPageWidget(d->photoUi); setLeftBottomPix(QIcon::fromTheme(QLatin1String("image-stack"))); slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } AdvPrintPhotoPage::~AdvPrintPhotoPage() { delete d->printer; delete d->pageSetupDlg; delete d; } void AdvPrintPhotoPage::initializePage() { d->photoUi->mPrintList->listView()->selectAll(); d->photoUi->mPrintList->slotRemoveItems(); if (d->settings->selMode == AdvPrintSettings::IMAGES) { d->photoUi->mPrintList->loadImagesFromCurrentSelection(); } else { d->wizard->setItemsList(d->settings->inputImages); } initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (d->settings->savedPhotoSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // reset preview page number d->settings->currentPreviewPage = 0; // create our photo sizes list d->wizard->previewPhotos(); int gid = d->photoUi->m_printer_choice->findText(d->settings->outputName(AdvPrintSettings::GIMP)); if (d->settings->gimpPath.isEmpty()) { // Gimp is not available : we disable the option. d->photoUi->m_printer_choice->setItemData(gid, false, Qt::UserRole-1); } int index = d->photoUi->m_printer_choice->findText(d->settings->printerName); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); d->photoUi->ListPhotoSizes->setIconSize(QSize(32, 32)); initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); } bool AdvPrintPhotoPage::validatePage() { d->settings->inputImages = d->photoUi->mPrintList->imageUrls(); d->settings->printerName = d->photoUi->m_printer_choice->itemHighlighted(); if (d->photoUi->ListPhotoSizes->currentItem()) { d->settings->savedPhotoSize = d->photoUi->ListPhotoSizes->currentItem()->text(); } return true; } bool AdvPrintPhotoPage::isComplete() const { return (!d->photoUi->mPrintList->imageUrls().isEmpty() || !d->wizard->itemsList().isEmpty()); } QPrinter* AdvPrintPhotoPage::printer() const { return d->printer; } DItemsList* AdvPrintPhotoPage::imagesList() const { return d->photoUi->mPrintList; } Ui_AdvPrintPhotoPage* AdvPrintPhotoPage::ui() const { return d->photoUi; } void AdvPrintPhotoPage::slotOutputChanged(const QString& text) { if (AdvPrintSettings::outputNames().values().contains(text)) { delete d->printer; d->printer = new QPrinter(); d->printer->setOutputFormat(QPrinter::PdfFormat); } else // real printer { for (QList::iterator it = d->printerList.begin() ; it != d->printerList.end() ; ++it) { if (it->printerName() == text) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Chosen printer: " << it->printerName(); delete d->printer; d->printer = new QPrinter(*it); } } d->printer->setOutputFormat(QPrinter::NativeFormat); } // default no margins d->printer->setFullPage(true); d->printer->setPageMargins(0, 0, 0, 0, QPrinter::Millimeter); } void AdvPrintPhotoPage::slotXMLLoadElement(QXmlStreamReader& xmlReader) { if (d->settings->photos.size()) { // read image is the last. AdvPrintPhoto* const pPhoto = d->settings->photos[d->settings->photos.size()-1]; qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " invoked " << xmlReader.name(); while (xmlReader.readNextStartElement()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << pPhoto->m_url << " " << xmlReader.name(); if (xmlReader.name() == QLatin1String("pa_caption")) { //useless this item has been added now if (pPhoto->m_pAdvPrintCaptionInfo) delete pPhoto->m_pAdvPrintCaptionInfo; pPhoto->m_pAdvPrintCaptionInfo = new AdvPrintCaptionInfo(); // get all attributes and its value of a tag in attrs variable. QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("type")); bool ok; if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionType = (AdvPrintSettings::CaptionType)attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("font")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.fromString(attr.toString()); } attr = attrs.value(QLatin1String("color")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.setNamedColor(attr.toString()); } attr = attrs.value(QLatin1String("size")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionSize = attr.toString().toInt(&ok); } attr = attrs.value(QLatin1String("text")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); pPhoto->m_pAdvPrintCaptionInfo->m_captionText = attr.toString(); } } } } } void AdvPrintPhotoPage::slotXMLSaveItem(QXmlStreamWriter& xmlWriter, int itemIndex) { if (d->settings->photos.size()) { AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; // TODO: first and copies could be removed since they are not useful any more xmlWriter.writeAttribute(QLatin1String("first"), QString::fromUtf8("%1") .arg(pPhoto->m_first)); xmlWriter.writeAttribute(QLatin1String("copies"), QString::fromUtf8("%1") .arg(pPhoto->m_first ? pPhoto->m_copies : 0)); // additional info (caption... etc) if (pPhoto->m_pAdvPrintCaptionInfo) { xmlWriter.writeStartElement(QLatin1String("pa_caption")); xmlWriter.writeAttribute(QLatin1String("type"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionType)); xmlWriter.writeAttribute(QLatin1String("font"), pPhoto->m_pAdvPrintCaptionInfo->m_captionFont.toString()); xmlWriter.writeAttribute(QLatin1String("size"), QString::fromUtf8("%1").arg(pPhoto->m_pAdvPrintCaptionInfo->m_captionSize)); xmlWriter.writeAttribute(QLatin1String("color"), pPhoto->m_pAdvPrintCaptionInfo->m_captionColor.name()); xmlWriter.writeAttribute(QLatin1String("text"), pPhoto->m_pAdvPrintCaptionInfo->m_captionText); xmlWriter.writeEndElement(); // pa_caption } } } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamWriter& xmlWriter) { xmlWriter.writeStartElement(QLatin1String("pa_layout")); xmlWriter.writeAttribute(QLatin1String("Printer"), d->photoUi->m_printer_choice->itemHighlighted()); xmlWriter.writeAttribute(QLatin1String("PageSize"), QString::fromUtf8("%1").arg(d->printer->paperSize())); xmlWriter.writeAttribute(QLatin1String("PhotoSize"), d->photoUi->ListPhotoSizes->currentItem()->text()); xmlWriter.writeEndElement(); // pa_layout } void AdvPrintPhotoPage::slotContextMenuRequested() { if (d->settings->photos.size()) { int itemIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); d->photoUi->mPrintList->listView()->blockSignals(true); QMenu menu(d->photoUi->mPrintList->listView()); QAction* const action = menu.addAction(i18n("Add again")); connect(action, SIGNAL(triggered()), this , SLOT(slotIncreaseCopies())); AdvPrintPhoto* const pPhoto = d->settings->photos[itemIndex]; qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " copies " << pPhoto->m_copies << " first " << pPhoto->m_first; if (pPhoto->m_copies > 1 || !pPhoto->m_first) { QAction* const actionr = menu.addAction(i18n("Remove")); connect(actionr, SIGNAL(triggered()), this, SLOT(slotDecreaseCopies())); } menu.exec(QCursor::pos()); d->photoUi->mPrintList->listView()->blockSignals(false); } } void AdvPrintPhotoPage::slotIncreaseCopies() { if (d->settings->photos.size()) { QList list; DItemsListViewItem* const item = dynamic_cast(d->photoUi->mPrintList->listView()->currentItem()); if (!item) { return; } list.append(item->url()); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " Adding a copy of " << item->url(); d->photoUi->mPrintList->slotAddImages(list); } } void AdvPrintPhotoPage::slotDecreaseCopies() { if (d->settings->photos.size()) { DItemsListViewItem* const item = dynamic_cast (d->photoUi->mPrintList->listView()->currentItem()); if (!item) { return; } qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " Removing a copy of " << item->url(); d->photoUi->mPrintList->slotRemoveItems(); } } void AdvPrintPhotoPage::slotAddItems(const QList& list) { if (list.count() == 0) { return; } QList urls; d->photoUi->mPrintList->blockSignals(true); for (QList::ConstIterator it = list.constBegin() ; it != list.constEnd() ; ++it) { QUrl imageUrl = *it; // Check if the new item already exist in the list. bool found = false; for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && (pCurrentPhoto->m_url == imageUrl) && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies++; found = true; AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(*pCurrentPhoto); pPhoto->m_first = false; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Added fileName: " << pPhoto->m_url.fileName() << " copy number " << pCurrentPhoto->m_copies; } } if (!found) { AdvPrintPhoto* const pPhoto = new AdvPrintPhoto(150, d->iface); pPhoto->m_url = *it; pPhoto->m_first = true; d->settings->photos.append(pPhoto); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Added new fileName: " << pPhoto->m_url.fileName(); } } d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.size()) { setComplete(true); } } void AdvPrintPhotoPage::slotRemovingItems(const QList& list) { if (list.count() == 0) { return; } d->photoUi->mPrintList->blockSignals(true); foreach (int itemIndex, list) { if (d->settings->photos.size() && itemIndex >= 0) { /// Debug data: found and copies bool found = false; int copies = 0; AdvPrintPhoto* const pPhotoToRemove = d->settings->photos.at(itemIndex); // photo to be removed could be: // 1) unique => just remove it // 2) first of n, => // search another with the same url // and set it a first and with a count to n-1 then remove it // 3) one of n, search the first one and set count to n-1 then remove it if (pPhotoToRemove && pPhotoToRemove->m_first) { if (pPhotoToRemove->m_copies > 0) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url) { pCurrentPhoto->m_copies = pPhotoToRemove->m_copies - 1; copies = pCurrentPhoto->m_copies; pCurrentPhoto->m_first = true; found = true; } } } // otherwise it's unique } else if (pPhotoToRemove) { for (int i = 0 ; i < d->settings->photos.count() && !found ; ++i) { AdvPrintPhoto* const pCurrentPhoto = d->settings->photos.at(i); if (pCurrentPhoto && pCurrentPhoto->m_url == pPhotoToRemove->m_url && pCurrentPhoto->m_first) { pCurrentPhoto->m_copies--; copies = pCurrentPhoto->m_copies; found = true; } } } else { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " NULL AdvPrintPhoto object "; return; } if (pPhotoToRemove) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Removed fileName: " << pPhotoToRemove->m_url.fileName() << " copy number " << copies; } d->settings->photos.removeAt(itemIndex); delete pPhotoToRemove; } } d->wizard->previewPhotos(); d->photoUi->mPrintList->blockSignals(false); d->photoUi->LblPhotoCount->setText(QString::number(d->settings->photos.count())); if (d->settings->photos.isEmpty()) { // No photos => disabling next button (e.g. crop page) setComplete(false); } } void AdvPrintPhotoPage::slotBtnPrintOrderDownClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Moved photo " << currentIndex - 1 << " to " << currentIndex; #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) d->settings->photos.swapItemsAt(currentIndex, currentIndex - 1); #else d->settings->photos.swap(currentIndex, currentIndex - 1); #endif d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPrintOrderUpClicked() { d->photoUi->mPrintList->blockSignals(true); int currentIndex = d->photoUi->mPrintList->listView()->currentIndex().row(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Moved photo " << currentIndex << " to " << currentIndex + 1; #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) d->settings->photos.swapItemsAt(currentIndex, currentIndex + 1); #else d->settings->photos.swap(currentIndex, currentIndex + 1); #endif d->photoUi->mPrintList->blockSignals(false); d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotXMLCustomElement(QXmlStreamReader& xmlReader) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " invoked " << xmlReader.name(); while (!xmlReader.atEnd()) { if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String("pa_layout")) { bool ok; QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef attr = attrs.value(QLatin1String("Printer")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); int index = d->photoUi->m_printer_choice->findText(attr.toString()); if (index != -1) { d->photoUi->m_printer_choice->setCurrentIndex(index); } slotOutputChanged(d->photoUi->m_printer_choice->itemHighlighted()); } attr = attrs.value(QLatin1String("PageSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); QPrinter::PaperSize paperSize = (QPrinter::PaperSize)attr.toString().toInt(&ok); d->printer->setPaperSize(paperSize); } attr = attrs.value(QLatin1String("PhotoSize")); if (!attr.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " found " << attr.toString(); d->settings->savedPhotoSize = attr.toString(); } } xmlReader.readNext(); } // reset preview page number d->settings->currentPreviewPage = 0; initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); QList list = d->photoUi->ListPhotoSizes->findItems(d->settings->savedPhotoSize, Qt::MatchExactly); if (list.count()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " PhotoSize " << list[0]->text(); d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); } else { d->photoUi->ListPhotoSizes->setCurrentRow(0); } d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageDownClicked() { if (d->settings->currentPreviewPage == 0) return; d->settings->currentPreviewPage--; d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotBtnPreviewPageUpClicked() { if (d->settings->currentPreviewPage == getPageCount() - 1) return; d->settings->currentPreviewPage++; d->wizard->previewPhotos(); } int AdvPrintPhotoPage::getPageCount() const { int pageCount = 0; int photoCount = d->settings->photos.count(); if (photoCount > 0) { // get the selected layout AdvPrintPhotoSize* const s = d->settings->photosizes.at(d->photoUi->ListPhotoSizes->currentRow()); // how many pages? Recall that the first layout item is the paper size int photosPerPage = s->m_layouts.count() - 1; int remainder = photoCount % photosPerPage; int emptySlots = 0; if (remainder > 0) emptySlots = photosPerPage - remainder; pageCount = photoCount / photosPerPage; if (emptySlots > 0) pageCount++; } return pageCount; } void AdvPrintPhotoPage::createPhotoGrid(AdvPrintPhotoSize* const p, int pageWidth, int pageHeight, int rows, int columns, TemplateIcon* const iconpreview) { // To prevent divide by 0. if (!columns) columns = 1; if (!rows) rows = 1; int MARGIN = (int)(((double)pageWidth + (double)pageHeight) / 2.0 * 0.04 + 0.5); int GAP = MARGIN / 4; int photoWidth = (pageWidth - (MARGIN * 2) - ((columns - 1) * GAP)) / columns; int photoHeight = (pageHeight - (MARGIN * 2) - ((rows - 1) * GAP)) / rows; int row = 0; for (int y = MARGIN ; (row < rows) && (y < (pageHeight - MARGIN)) ; y += (photoHeight + GAP)) { int col = 0; for (int x = MARGIN ; (col < columns) && (x < (pageWidth - MARGIN)) ; x += (photoWidth + GAP)) { p->m_layouts.append(new QRect(x, y, photoWidth, photoHeight)); iconpreview->fillRect(x, y, photoWidth, photoHeight, Qt::color1); ++col; } ++row; } } void AdvPrintPhotoPage::slotListPhotoSizesSelected() { AdvPrintPhotoSize* s = nullptr; QSizeF size, sizeManaged; // TODO FREE STYLE // check if layout is managed by templates or free one // get the selected layout int curr = d->photoUi->ListPhotoSizes->currentRow(); QListWidgetItem* item = d->photoUi->ListPhotoSizes->item(curr); // if custom page layout we launch a dialog to choose what kind if (item->text() == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { // check if a custom layout has already been added if (curr >= 0 && curr < d->settings->photosizes.size()) { s = d->settings->photosizes.at(curr); d->settings->photosizes.removeAt(curr); delete s; s = nullptr; } QPointer custDlg = new AdvPrintCustomLayoutDlg(this); custDlg->readSettings(); custDlg->exec(); custDlg->saveSettings(); // get parameters from dialog size = d->settings->pageSize; int scaleValue = 10; // 0.1 mm // convert to mm if (custDlg->m_photoUnits->currentText() == i18n("inches")) { size /= 25.4; scaleValue = 1000; } else if (custDlg->m_photoUnits->currentText() == i18n("cm")) { size /= 10; scaleValue = 100; } sizeManaged = size * scaleValue; s = new AdvPrintPhotoSize; TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); if (custDlg->m_photoGridCheck->isChecked()) { // custom photo grid int rows = custDlg->m_gridRows->value(); int columns = custDlg->m_gridColumns->value(); s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int pageWidth = (int)(size.width()) * scaleValue; int pageHeight = (int)(size.height()) * scaleValue; createPhotoGrid(s, pageWidth, pageHeight, rows, columns, &iconpreview); } else if (custDlg->m_fitAsManyCheck->isChecked()) { double width = custDlg->m_photoWidth->value(); double height = custDlg->m_photoHeight->value(); //photo size must be less than page size static const float round_value = 0.01F; if ((height > (size.height() + round_value) || width > (size.width() + round_value))) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "photo size " << QSizeF(width, height) << "> page size " << size; delete s; s = nullptr; } else { // fit as many photos of given size as possible s->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); s->m_autoRotate = custDlg->m_autorotate->isChecked(); s->m_label = item->text(); s->m_dpi = 0; int nColumns = int(size.width() / width); int nRows = int(size.height() / height); int spareWidth = int(size.width()) % int(width); // check if there's no room left to separate photos if (nColumns > 1 && spareWidth == 0) { nColumns -= 1; spareWidth = width; } int spareHeight = int(size.height()) % int(height); // check if there's no room left to separate photos if (nRows > 1 && spareHeight == 0) { nRows -= 1; spareHeight = int(height); } if (nRows > 0 && nColumns > 0) { // n photos => dx1, photo1, dx2, photo2,... photoN, dxN+1 int dx = spareWidth * scaleValue / (nColumns + 1); int dy = spareHeight * scaleValue / (nRows + 1); int photoX = 0; int photoY = 0; width *= scaleValue; height *= scaleValue; for (int row = 0 ; row < nRows ; ++row) { photoY = dy * (row + 1) + (row * height); for (int col = 0 ; col < nColumns ; ++col) { photoX = dx * (col + 1) + (col * width); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "photo at P(" << photoX << ", " << photoY << ") size(" << width << ", " << height; s->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } } } else { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "I cannot go on, rows " << nRows << "> columns " << nColumns; delete s; s = nullptr; } } } else { // Atckin's layout } // TODO not for Atckin's layout iconpreview.end(); if (s) { s->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(s); } delete custDlg; } else { s = d->settings->photosizes.at(curr); } // reset preview page number d->settings->currentPreviewPage = 0; if (!s) { QMessageBox::warning(this, i18n("Custom layout"), i18n("The selected custom photo size can " "not be applied to the paper size.")); // change position to top d->photoUi->ListPhotoSizes->setCurrentRow(0); } d->wizard->previewPhotos(); } void AdvPrintPhotoPage::slotPageSetup() { delete d->pageSetupDlg; QString lastSize = d->photoUi->ListPhotoSizes->currentItem()->text(); d->pageSetupDlg = new QPageSetupDialog(d->printer, this); int ret = d->pageSetupDlg->exec(); if (ret == QDialog::Accepted) { QPrinter* const printer = d->pageSetupDlg->printer(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Dialog exit, new size " << printer->paperSize(QPrinter::Millimeter) << " internal size " << d->printer->paperSize(QPrinter::Millimeter); qreal left, top, right, bottom; d->printer->getPageMargins(&left, &top, &right, &bottom, QPrinter::Millimeter); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Dialog exit, new margins: left " << left << " right " << right << " top " << top << " bottom " << bottom; // next should be useless invoke once changing wizard page //d->wizard->initPhotoSizes(d->printer.paperSize(QPrinter::Millimeter)); //d->settings->pageSize = d->printer.paperSize(QPrinter::Millimeter); #ifdef DEBUG qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " dialog exited num of copies: " << printer->numCopies() << " inside: " << d->printer->numCopies(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " dialog exited from : " << printer->fromPage() << " to: " << d->printer->toPage(); #endif } // Fix the page size dialog and preview PhotoPage initPhotoSizes(d->printer->paperSize(QPrinter::Millimeter)); // restore photoSize if (lastSize == i18n(CUSTOM_PAGE_LAYOUT_NAME)) { d->photoUi->ListPhotoSizes->setCurrentRow(0); } else { QList list = d->photoUi->ListPhotoSizes->findItems(lastSize, Qt::MatchExactly); if (list.count()) d->photoUi->ListPhotoSizes->setCurrentItem(list[0]); else d->photoUi->ListPhotoSizes->setCurrentRow(0); } // create our photo sizes list d->wizard->previewPhotos(); } void AdvPrintPhotoPage::manageBtnPreviewPage() { if (d->settings->photos.isEmpty()) { d->photoUi->BtnPreviewPageDown->setEnabled(false); d->photoUi->BtnPreviewPageUp->setEnabled(false); } else { d->photoUi->BtnPreviewPageDown->setEnabled(true); d->photoUi->BtnPreviewPageUp->setEnabled(true); if (d->settings->currentPreviewPage == 0) { d->photoUi->BtnPreviewPageDown->setEnabled(false); } if ((d->settings->currentPreviewPage + 1) == getPageCount()) { d->photoUi->BtnPreviewPageUp->setEnabled(false); } } } void AdvPrintPhotoPage::initPhotoSizes(const QSizeF& pageSize) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "New page size " << pageSize << ", old page size " << d->settings->pageSize; // don't refresh anything if we haven't changed page sizes. if (pageSize == d->settings->pageSize) { return; } d->settings->pageSize = pageSize; // cleaning pageSize memory before invoking clear() for (int i = 0 ; i < d->settings->photosizes.count() ; ++i) { delete d->settings->photosizes.at(i); } d->settings->photosizes.clear(); // get template-files and parse them QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << QLatin1String("*.xml")); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Template XML files list: " << list; - foreach(const QString& fn, list) + foreach (const QString& fn, list) { parseTemplateFile(dir.absolutePath() + QLatin1Char('/') + fn, pageSize); } qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "photosizes count() =" << d->settings->photosizes.count(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "photosizes isEmpty() =" << d->settings->photosizes.isEmpty(); if (d->settings->photosizes.isEmpty()) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Empty photoSize-list, create default size"; // There is no valid page size yet. Create a default page (B10) to prevent crashes. AdvPrintPhotoSize* const p = new AdvPrintPhotoSize; // page size: B10 (32 x 45 mm) p->m_layouts.append(new QRect(0, 0, 3200, 4500)); p->m_layouts.append(new QRect(0, 0, 3200, 4500)); // add to the list d->settings->photosizes.append(p); } // load the photo sizes into the listbox d->photoUi->ListPhotoSizes->blockSignals(true); d->photoUi->ListPhotoSizes->clear(); QList::iterator it; for (it = d->settings->photosizes.begin() ; it != d->settings->photosizes.end() ; ++it) { AdvPrintPhotoSize* const s = static_cast(*it); if (s) { QListWidgetItem* const pWItem = new QListWidgetItem(s->m_label); pWItem->setIcon(s->m_icon); d->photoUi->ListPhotoSizes->addItem(pWItem); } } // Adding custom choice QListWidgetItem* const pWItem = new QListWidgetItem(i18n(CUSTOM_PAGE_LAYOUT_NAME)); TemplateIcon ti(80, pageSize.toSize()); ti.begin(); QPainter& painter = ti.getPainter(); painter.setPen(Qt::color1); painter.drawText(painter.viewport(), Qt::AlignCenter, i18n("Custom\nlayout")); ti.end(); pWItem->setIcon(ti.getIcon()); d->photoUi->ListPhotoSizes->addItem(pWItem); d->photoUi->ListPhotoSizes->blockSignals(false); d->photoUi->ListPhotoSizes->setCurrentRow(0, QItemSelectionModel::Select); } void AdvPrintPhotoPage::parseTemplateFile(const QString& fn, const QSizeF& pageSize) { QDomDocument doc(QLatin1String("mydocument")); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " XXX: " << fn; if (fn.isEmpty()) { return; } QFile file(fn); if (!file.open(QIODevice::ReadOnly)) { return; } if (!doc.setContent(&file)) { file.close(); return; } file.close(); AdvPrintPhotoSize* p = nullptr; // print out the element names of all elements that are direct children // of the outermost element. QDomElement docElem = doc.documentElement(); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << docElem.tagName(); // the node really is an element. QSizeF size; QString unit; int scaleValue; QDomNode n = docElem.firstChild(); while (!n.isNull()) { size = QSizeF(0, 0); scaleValue = 10; // 0.1 mm QDomElement e = n.toElement(); // try to convert the node to an element. if (!e.isNull()) { if (e.tagName() == QLatin1String("paper")) { size = QSizeF(e.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(), e.attribute(QLatin1String("height"), QLatin1String("0")).toFloat()); unit = e.attribute(QLatin1String("unit"), QLatin1String("mm")); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << e.tagName() << QLatin1String(" name=") << e.attribute(QLatin1String("name"), QLatin1String("??")) << " size= " << size << " unit= " << unit; if (size == QSizeF(0.0, 0.0) && size == pageSize) { // skipping templates without page size since pageSize is not set n = n.nextSibling(); continue; } else if (unit != QLatin1String("mm") && size != QSizeF(0.0, 0.0)) // "cm", "inches" or "inch" { // convert to mm if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { size *= 25.4; scaleValue = 1000; qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "template size " << size << " page size " << pageSize; } else if (unit == QLatin1String("cm")) { size *= 10; scaleValue = 100; qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "template size " << size << " page size " << pageSize; } else { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Wrong unit " << unit << " skipping layout"; n = n.nextSibling(); continue; } } static const float round_value = 0.01F; if (size == QSizeF(0, 0)) { size = pageSize; unit = QLatin1String("mm"); } else if (pageSize != QSizeF(0, 0) && (size.height() > (pageSize.height() + round_value) || size.width() > (pageSize.width() + round_value))) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "skipping size " << size << " page size " << pageSize; // skipping layout it can't fit n = n.nextSibling(); continue; } // Next templates are good qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "layout size " << size << " page size " << pageSize; QDomNode np = e.firstChild(); while (!np.isNull()) { QDomElement ep = np.toElement(); // try to convert the node to an element. if (!ep.isNull()) { if (ep.tagName() == QLatin1String("template")) { p = new AdvPrintPhotoSize; QSizeF sizeManaged; // set page size if (pageSize == QSizeF(0, 0)) { sizeManaged = size * scaleValue; } else if (unit == QLatin1String("inches") || unit == QLatin1String("inch")) { sizeManaged = pageSize * scaleValue / 25.4; } else { sizeManaged = pageSize * 10; } p->m_layouts.append(new QRect(0, 0, (int)sizeManaged.width(), (int)sizeManaged.height())); // create a small preview of the template TemplateIcon iconpreview(80, sizeManaged.toSize()); iconpreview.begin(); QString desktopFileName = ep.attribute(QLatin1String("name"), QLatin1String("XXX")) + QLatin1String(".desktop"); QDir dir(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/templates"), QStandardPaths::LocateDirectory)); const QStringList list = dir.entryList(QStringList() << desktopFileName); qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Template desktop files list: " << list; QStringList::ConstIterator it = list.constBegin(); QStringList::ConstIterator end = list.constEnd(); if (it != end) { p->m_label = KDesktopFile(dir.absolutePath() + QLatin1Char('/') + *it).readName(); } else { p->m_label = ep.attribute(QLatin1String("name"), QLatin1String("XXX")); qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << "missed template translation " << desktopFileName; } p->m_dpi = ep.attribute(QLatin1String("dpi"), QLatin1String("0")).toInt(); p->m_autoRotate = (ep.attribute(QLatin1String("autorotate"), QLatin1String("false")) == QLatin1String("true")) ? true : false; QDomNode nt = ep.firstChild(); while (!nt.isNull()) { QDomElement et = nt.toElement(); // try to convert the node to an element. if (!et.isNull()) { if (et.tagName() == QLatin1String("photo")) { float value = et.attribute(QLatin1String("width"), QLatin1String("0")).toFloat(); int width = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("height"), QLatin1String("0")).toFloat(); int height = (int)((value == 0 ? size.height() : value) * scaleValue); int photoX = (int)((et.attribute(QLatin1String("x"), QLatin1String("0")).toFloat() * scaleValue)); int photoY = (int)((et.attribute(QLatin1String("y"), QLatin1String("0")).toFloat() * scaleValue)); p->m_layouts.append(new QRect(photoX, photoY, width, height)); iconpreview.fillRect(photoX, photoY, width, height, Qt::color1); } else if (et.tagName() == QLatin1String("photogrid")) { float value = et.attribute(QLatin1String("pageWidth"), QLatin1String("0")).toFloat(); int pageWidth = (int)((value == 0 ? size.width() : value) * scaleValue); value = et.attribute(QLatin1String("pageHeight"), QLatin1String("0")).toFloat(); int pageHeight = (int)((value == 0 ? size.height() : value) * scaleValue); int rows = et.attribute(QLatin1String("rows"), QLatin1String("0")).toInt(); int columns = et.attribute(QLatin1String("columns"), QLatin1String("0")).toInt(); if (rows > 0 && columns > 0) { createPhotoGrid(p, pageWidth, pageHeight, rows, columns, &iconpreview); } else { qCWarning(DIGIKAM_DPLUGIN_GENERIC_LOG) << " Wrong grid configuration, rows " << rows << ", columns " << columns; } } else { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " " << et.tagName(); } } nt = nt.nextSibling(); } iconpreview.end(); p->m_icon = iconpreview.getIcon(); d->settings->photosizes.append(p); } else { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "? " << ep.tagName() << " attr=" << ep.attribute(QLatin1String("name"), QLatin1String("??")); } } np = np.nextSibling(); } } else { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "??" << e.tagName() << " name=" << e.attribute(QLatin1String("name"), QLatin1String("??")); } } n = n.nextSibling(); } } } // namespace DigikamGenericPrintCreatorPlugin diff --git a/core/dplugins/generic/tools/sendbymail/wizard/mailalbumspage.cpp b/core/dplugins/generic/tools/sendbymail/wizard/mailalbumspage.cpp index e5360bd20c..0850c595c9 100644 --- a/core/dplugins/generic/tools/sendbymail/wizard/mailalbumspage.cpp +++ b/core/dplugins/generic/tools/sendbymail/wizard/mailalbumspage.cpp @@ -1,113 +1,113 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-27 * Description : a tool to export items by email. * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "mailalbumspage.h" // Qt includes #include #include // Local includes #include "mailwizard.h" namespace DigikamGenericSendByMailPlugin { class Q_DECL_HIDDEN MailAlbumsPage::Private { public: explicit Private(QWizard* const dialog) : albumSupport(false), albumSelector(nullptr), wizard(nullptr), iface(nullptr) { wizard = dynamic_cast(dialog); if (wizard) { iface = wizard->iface(); } } bool albumSupport; QWidget* albumSelector; MailWizard* wizard; DInfoInterface* iface; }; MailAlbumsPage::MailAlbumsPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private(dialog)) { if (d->iface) { d->albumSelector = d->iface->albumChooser(this); connect(d->iface, SIGNAL(signalAlbumChooserSelectionChanged()), this, SIGNAL(completeChanged())); } else { d->albumSelector = new QWidget(this); } setPageWidget(d->albumSelector); setLeftBottomPix(QIcon::fromTheme(QLatin1String("folder-mail"))); } MailAlbumsPage::~MailAlbumsPage() { delete d; } bool MailAlbumsPage::validatePage() { if (!d->iface) return false; if (d->iface->albumChooserItems().isEmpty()) return false; d->wizard->settings()->inputImages.clear(); // update image list with album contents. - foreach(const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) + foreach (const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) { d->wizard->settings()->inputImages << url; } return true; } bool MailAlbumsPage::isComplete() const { if (!d->iface) return false; return (!d->iface->albumChooserItems().isEmpty()); } } // namespace DigikamGenericSendByMailPlugin diff --git a/core/dplugins/generic/tools/videoslideshow/wizard/vidslidealbumspage.cpp b/core/dplugins/generic/tools/videoslideshow/wizard/vidslidealbumspage.cpp index fa229ffedb..4aad64d8de 100644 --- a/core/dplugins/generic/tools/videoslideshow/wizard/vidslidealbumspage.cpp +++ b/core/dplugins/generic/tools/videoslideshow/wizard/vidslidealbumspage.cpp @@ -1,113 +1,113 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-25 * Description : a tool to generate video slideshow from images. * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "vidslidealbumspage.h" // Qt includes #include #include // Local includes #include "vidslidewizard.h" namespace DigikamGenericVideoSlideShowPlugin { class Q_DECL_HIDDEN VidSlideAlbumsPage::Private { public: explicit Private(QWizard* const dialog) : albumSupport(false), albumSelector(nullptr), wizard(nullptr), iface(nullptr) { wizard = dynamic_cast(dialog); if (wizard) { iface = wizard->iface(); } } bool albumSupport; QWidget* albumSelector; VidSlideWizard* wizard; DInfoInterface* iface; }; VidSlideAlbumsPage::VidSlideAlbumsPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private(dialog)) { if (d->iface) { d->albumSelector = d->iface->albumChooser(this); connect(d->iface, SIGNAL(signalAlbumChooserSelectionChanged()), this, SIGNAL(completeChanged())); } else { d->albumSelector = new QWidget(this); } setPageWidget(d->albumSelector); setLeftBottomPix(QIcon::fromTheme(QLatin1String("folder-pictures"))); } VidSlideAlbumsPage::~VidSlideAlbumsPage() { delete d; } bool VidSlideAlbumsPage::validatePage() { if (!d->iface) return false; if (d->iface && d->iface->albumChooserItems().isEmpty()) return false; d->wizard->settings()->inputImages.clear(); // update image list with album contents. - foreach(const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) + foreach (const QUrl& url, d->iface->albumsItems(d->iface->albumChooserItems())) { d->wizard->settings()->inputImages << url; } return true; } bool VidSlideAlbumsPage::isComplete() const { if (!d->iface) return false; return (!d->iface->albumChooserItems().isEmpty()); } } // namespace DigikamGenericVideoSlideShowPlugin diff --git a/core/dplugins/generic/tools/videoslideshow/wizard/vidslidefinalpage.cpp b/core/dplugins/generic/tools/videoslideshow/wizard/vidslidefinalpage.cpp index 14222a96bf..58bb9d0ed6 100644 --- a/core/dplugins/generic/tools/videoslideshow/wizard/vidslidefinalpage.cpp +++ b/core/dplugins/generic/tools/videoslideshow/wizard/vidslidefinalpage.cpp @@ -1,226 +1,226 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-05-25 * Description : a tool to generate video slideshow from images. * * Copyright (C) 2017-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "vidslidefinalpage.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "vidslidewizard.h" #include "dlayoutbox.h" #include "digikam_debug.h" #include "dprogresswdg.h" #include "dhistoryview.h" #include "vidslidethread.h" #include "vidplayerdlg.h" namespace DigikamGenericVideoSlideShowPlugin { class Q_DECL_HIDDEN VidSlideFinalPage::Private { public: explicit Private(QWizard* const dialog) : progressView(nullptr), progressBar(nullptr), complete(false), encoder(nullptr), wizard(nullptr), settings(nullptr), iface(nullptr) { wizard = dynamic_cast(dialog); if (wizard) { iface = wizard->iface(); settings = wizard->settings(); } } DHistoryView* progressView; DProgressWdg* progressBar; bool complete; VidSlideThread* encoder; VidSlideWizard* wizard; VidSlideSettings* settings; DInfoInterface* iface; }; VidSlideFinalPage::VidSlideFinalPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private(dialog)) { setObjectName(QLatin1String("FinalPage")); DVBox* const vbox = new DVBox(this); d->progressView = new DHistoryView(vbox); d->progressBar = new DProgressWdg(vbox); vbox->setStretchFactor(d->progressBar, 10); vbox->setContentsMargins(QMargins()); vbox->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setPageWidget(vbox); setLeftBottomPix(QIcon::fromTheme(QLatin1String("system-run"))); } VidSlideFinalPage::~VidSlideFinalPage() { if (d->encoder) d->encoder->cancel(); delete d; } void VidSlideFinalPage::initializePage() { d->complete = false; emit completeChanged(); QTimer::singleShot(0, this, SLOT(slotProcess())); } void VidSlideFinalPage::slotProcess() { if (!d->wizard) { d->progressView->addEntry(i18n("Internal Error"), DHistoryView::ErrorEntry); return; } d->progressView->clear(); d->progressBar->reset(); d->progressView->addEntry(i18n("Starting to generate video slideshow..."), DHistoryView::ProgressEntry); d->progressView->addEntry(i18n("%1 input images to process", d->settings->inputImages.count()), DHistoryView::ProgressEntry); - foreach(const QUrl& url, d->settings->inputImages) + foreach (const QUrl& url, d->settings->inputImages) { d->progressView->addEntry(QDir::toNativeSeparators(url.toLocalFile()), DHistoryView::ProgressEntry); } if (!d->settings->inputAudio.isEmpty()) { d->progressView->addEntry(i18n("%1 input audio stream to process:", d->settings->inputAudio.count()), DHistoryView::ProgressEntry); - foreach(const QUrl& url, d->settings->inputAudio) + foreach (const QUrl& url, d->settings->inputAudio) { d->progressView->addEntry(QDir::toNativeSeparators(url.toLocalFile()), DHistoryView::ProgressEntry); } } d->progressBar->setMinimum(0); d->progressBar->setMaximum(d->settings->inputImages.count()); d->encoder = new VidSlideThread(this); connect(d->encoder, SIGNAL(signalProgress(int)), d->progressBar, SLOT(setValue(int))); connect(d->encoder, SIGNAL(signalMessage(QString,bool)), this, SLOT(slotMessage(QString,bool))); connect(d->encoder, SIGNAL(signalDone(bool)), this, SLOT(slotDone(bool))); d->encoder->processStream(d->settings); d->encoder->start(); } void VidSlideFinalPage::cleanupPage() { if (d->encoder) d->encoder->cancel(); } void VidSlideFinalPage::slotMessage(const QString& mess, bool err) { d->progressView->addEntry(mess, err ? DHistoryView::ErrorEntry : DHistoryView::ProgressEntry); } void VidSlideFinalPage::slotDone(bool completed) { d->progressBar->progressCompleted(); d->complete = completed; if (!d->complete) { d->progressView->addEntry(i18n("Video Slideshow is not completed"), DHistoryView::WarningEntry); } else { d->progressView->addEntry(i18n("Video Slideshow completed."), DHistoryView::ProgressEntry); if (d->settings->outputPlayer != VidSlideSettings::NOPLAYER) { d->progressView->addEntry(i18n("Opening video stream in player..."), DHistoryView::ProgressEntry); if (d->settings->outputPlayer == VidSlideSettings::INTERNAL) { VidPlayerDlg* const player = new VidPlayerDlg(d->settings->outputVideo, this); player->show(); player->resize(800, 600); } else { QDesktopServices::openUrl(QUrl::fromLocalFile(d->settings->outputVideo)); } } } emit completeChanged(); } bool VidSlideFinalPage::isComplete() const { return d->complete; } } // namespace DigikamGenericVideoSlideShowPlugin diff --git a/core/dplugins/generic/view/presentation/audio/presentationaudiolist.cpp b/core/dplugins/generic/view/presentation/audio/presentationaudiolist.cpp index f750be7c7e..98cf43747d 100644 --- a/core/dplugins/generic/view/presentation/audio/presentationaudiolist.cpp +++ b/core/dplugins/generic/view/presentation/audio/presentationaudiolist.cpp @@ -1,245 +1,245 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-14 * Description : a presentation tool. * * Copyright (C) 2008-2009 by Valerio Fuoglio * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2012-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "presentationaudiolist.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // QtAV includes #include // krazy:exclude=includes #include // krazy:exclude=includes // KDE includes #include // Local includes #include "digikam_debug.h" using namespace QtAV; namespace DigikamGenericPresentationPlugin { class Q_DECL_HIDDEN PresentationAudioListItem::Private { public: explicit Private() { mediaObject = nullptr; } QUrl url; QString artist; QString title; QTime totalTime; AVPlayer* mediaObject; }; PresentationAudioListItem::PresentationAudioListItem(QListWidget* const parent, const QUrl& url) : QListWidgetItem(parent), d(new Private) { d->url = url; setIcon(QIcon::fromTheme(QLatin1String("audio-x-generic")).pixmap(48, QIcon::Disabled)); d->totalTime = QTime(0, 0, 0); d->mediaObject = new AVPlayer(this); connect(d->mediaObject, SIGNAL(mediaStatusChanged(QtAV::MediaStatus)), this, SLOT(slotMediaStateChanged(QtAV::MediaStatus))); connect(d->mediaObject, SIGNAL(error(QtAV::AVError)), this, SLOT(slotPlayerError(QtAV::AVError))); d->mediaObject->setFile(url.toLocalFile()); d->mediaObject->load(); } PresentationAudioListItem::~PresentationAudioListItem() { delete d; } QUrl PresentationAudioListItem::url() const { return d->url; } void PresentationAudioListItem::setName(const QString& text) { setText(text); } QString PresentationAudioListItem::artist() const { return d->artist; } QString PresentationAudioListItem::title() const { return d->title; } QTime PresentationAudioListItem::totalTime() const { return d->totalTime; } void PresentationAudioListItem::slotPlayerError(const QtAV::AVError& err) { if (err.error() != AVError::NoError) { qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "An error as occurred while playing (" << err.string() << ")"; showErrorDialog(err.string()); } } void PresentationAudioListItem::slotMediaStateChanged(QtAV::MediaStatus status) { if (status == QtAV::InvalidMedia) { showErrorDialog(i18n("No detail available")); return; } qint64 total = d->mediaObject->duration(); int hours = (int)(total / (long int)(60 * 60 * 1000)); int mins = (int)((total / (long int)(60 * 1000 )) - (long int)(hours * 60)); int secs = (int)((total / (long int)1000) - (long int)(hours * 60 * 60) - (long int)(mins * 60)); d->totalTime = QTime(hours, mins, secs); QHash meta = d->mediaObject->statistics().metadata; d->artist = meta.value(QLatin1String("artist")); d->title = meta.value(QLatin1String("title")); if ( d->artist.isEmpty() && d->title.isEmpty() ) setText(d->url.fileName()); else setText(i18nc("artist - title", "%1 - %2", artist(), title())); emit signalTotalTimeReady(d->url, d->totalTime); } void PresentationAudioListItem::showErrorDialog(const QString& err) { QPointer msgBox = new QMessageBox(QApplication::activeWindow()); msgBox->setWindowTitle(i18n("Error")); msgBox->setText(i18n("%1 may not be playable.", d->url.fileName())); msgBox->setDetailedText(err); msgBox->setStandardButtons(QMessageBox::Ok); msgBox->setDefaultButton(QMessageBox::Ok); msgBox->setIcon(QMessageBox::Critical); msgBox->exec(); d->artist = d->url.fileName(); d->title = i18n("This file may not be playable."); setText(i18nc("artist - title", "%1 - %2", artist(), title())); setBackground(QBrush(Qt::red)); setForeground(QBrush(Qt::white)); QFont errorFont = font(); errorFont.setBold(true); errorFont.setItalic(true); setFont(errorFont); delete msgBox; } // ------------------------------------------------------------------ PresentationAudioList::PresentationAudioList(QWidget* const parent) : QListWidget(parent) { setSelectionMode(QAbstractItemView::SingleSelection); setAcceptDrops(true); setSortingEnabled(false); setIconSize(QSize(32, 32)); } void PresentationAudioList::dragEnterEvent(QDragEnterEvent* e) { if (e->mimeData()->hasUrls()) e->acceptProposedAction(); } void PresentationAudioList::dragMoveEvent(QDragMoveEvent* e) { if (e->mimeData()->hasUrls()) e->acceptProposedAction(); } void PresentationAudioList::dropEvent(QDropEvent* e) { QList list = e->mimeData()->urls(); QList urls; - foreach(const QUrl &url, list) + foreach (const QUrl &url, list) { QFileInfo fi(url.toLocalFile()); if (fi.isFile() && fi.exists()) urls.append(QUrl(url)); } e->acceptProposedAction(); if (!urls.isEmpty()) emit signalAddedDropItems(urls); } QList PresentationAudioList::fileUrls() { QList files; for (int i = 0; i < count(); ++i) { PresentationAudioListItem* const sitem = dynamic_cast(item(i)); if (sitem) { files << QUrl(sitem->url()); } } return files; } } // namespace DigikamGenericPresentationPlugin diff --git a/core/dplugins/generic/webservices/filetransfer/ftexportwidget.cpp b/core/dplugins/generic/webservices/filetransfer/ftexportwidget.cpp index ccc0eeaf65..00b36caacd 100644 --- a/core/dplugins/generic/webservices/filetransfer/ftexportwidget.cpp +++ b/core/dplugins/generic/webservices/filetransfer/ftexportwidget.cpp @@ -1,205 +1,205 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-09-28 * Description : a tool to export image to a KIO accessible * location * * Copyright (C) 2006-2009 by Johannes Wienke * Copyright (C) 2011-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "ftexportwidget.h" // Qt includes #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "dfiledialog.h" #include "ditemslist.h" #include "wstoolutils.h" #include "dlayoutbox.h" namespace DigikamGenericFileTransferPlugin { class Q_DECL_HIDDEN FTExportWidget::Private { public: explicit Private() { targetLabel = nullptr; targetDialog = nullptr; targetSearchButton = nullptr; imageList = nullptr; } KUrlComboRequester* targetLabel; DFileDialog* targetDialog; QPushButton* targetSearchButton; QUrl targetUrl; DItemsList* imageList; }; FTExportWidget::FTExportWidget(DInfoInterface* const iface, QWidget* const parent) : QWidget(parent), d(new Private) { // setup remote target selection DHBox* const hbox = new DHBox(this); QLabel* const label = new QLabel(hbox); d->targetLabel = new KUrlComboRequester(hbox); if (d->targetLabel->button()) d->targetLabel->button()->hide(); d->targetLabel->comboBox()->setEditable(true); label->setText(i18n("Target location: ")); d->targetLabel->setWhatsThis(i18n("Sets the target address to upload the images to. " "This can be any address as used in Dolphin or Konqueror, " "e.g. ftp://my.server.org/sub/folder.")); d->targetSearchButton = new QPushButton(i18n("Select export location..."), this); d->targetSearchButton->setIcon(QIcon::fromTheme(QLatin1String("folder-remote"))); // setup image list d->imageList = new DItemsList(this); d->imageList->setObjectName(QLatin1String("FTExport ImagesList")); d->imageList->setIface(iface); d->imageList->loadImagesFromCurrentSelection(); d->imageList->setAllowRAW(true); d->imageList->listView()->setWhatsThis(i18n("This is the list of images to upload " "to the specified target.")); // layout dialog QVBoxLayout* const layout = new QVBoxLayout(this); layout->addWidget(hbox); layout->addWidget(d->targetSearchButton); layout->addWidget(d->imageList); layout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); layout->setContentsMargins(QMargins()); // ------------------------------------------------------------------------ connect(d->targetSearchButton, SIGNAL(clicked(bool)), this, SLOT(slotShowTargetDialogClicked(bool))); connect(d->targetLabel, SIGNAL(textChanged(QString)), this, SLOT(slotLabelUrlChanged())); // ------------------------------------------------------------------------ updateTargetLabel(); } FTExportWidget::~FTExportWidget() { delete d; } QUrl FTExportWidget::targetUrl() const { return d->targetUrl; } QList FTExportWidget::history() const { QList urls; for (int i = 0 ; i <= d->targetLabel->comboBox()->count() ; ++i) urls << QUrl(d->targetLabel->comboBox()->itemText(i)); return urls; } void FTExportWidget::setHistory(const QList& urls) { d->targetLabel->comboBox()->clear(); - foreach(const QUrl& url, urls) + foreach (const QUrl& url, urls) d->targetLabel->comboBox()->addUrl(url); } void FTExportWidget::setTargetUrl(const QUrl& url) { d->targetUrl = url; updateTargetLabel(); } void FTExportWidget::slotShowTargetDialogClicked(bool checked) { Q_UNUSED(checked); d->targetDialog = new DFileDialog(this, i18n("Select target..."), d->targetUrl.toString(), i18n("All Files (*)")); d->targetDialog->setAcceptMode(QFileDialog::AcceptSave); d->targetDialog->setFileMode(QFileDialog::Directory); d->targetDialog->setOptions(QFileDialog::ShowDirsOnly); if (d->targetDialog->exec() == QDialog::Accepted) { d->targetUrl = d->targetDialog->selectedUrls().isEmpty() ? QUrl() : d->targetDialog->selectedUrls().at(0); updateTargetLabel(); emit signalTargetUrlChanged(d->targetUrl); } delete d->targetDialog; } void FTExportWidget::updateTargetLabel() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Call for url " << d->targetUrl.toDisplayString() << ", valid = " << d->targetUrl.isValid(); QString urlString = i18n(""); if (d->targetUrl.isValid()) { urlString = d->targetUrl.toDisplayString(); d->targetLabel->setUrl(QUrl(urlString)); } } DItemsList* FTExportWidget::imagesList() const { return d->imageList; } void FTExportWidget::slotLabelUrlChanged() { d->targetUrl = d->targetLabel->url(); emit signalTargetUrlChanged(d->targetUrl); } } // namespace DigikamGenericFileTransferPlugin diff --git a/core/dplugins/generic/webservices/flickr/flickrtalker.cpp b/core/dplugins/generic/webservices/flickr/flickrtalker.cpp index 85bf0e61f6..691825e8ac 100644 --- a/core/dplugins/generic/webservices/flickr/flickrtalker.cpp +++ b/core/dplugins/generic/webservices/flickr/flickrtalker.cpp @@ -1,1178 +1,1178 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-07 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2009 by Vardhman Jain * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2017 by Maik Qualmann * * 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, 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. * * ============================================================ */ #include "flickrtalker.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dmetadata.h" #include "wstoolutils.h" #include "flickrmpform.h" #include "flickrwindow.h" #include "digikam_debug.h" #include "digikam_version.h" #include "previewloadthread.h" // OAuth2 library includes #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wextra-semi" #endif #include "o1.h" #include "o0globals.h" #include "o1requestor.h" #include "o0settingsstore.h" #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif namespace DigikamGenericFlickrPlugin { class Q_DECL_HIDDEN FlickrTalker::Private { public: explicit Private() { parent = nullptr; netMngr = nullptr; reply = nullptr; settings = nullptr; state = FE_LOGOUT; iface = nullptr; o1 = nullptr; store = nullptr; requestor = nullptr; } QWidget* parent; QString serviceName; QString apiUrl; QString authUrl; QString tokenUrl; QString accessUrl; QString uploadUrl; QString apikey; QString secret; QString maxSize; QString username; QString userId; QString lastTmpFile; QNetworkAccessManager* netMngr; QNetworkReply* reply; QSettings* settings; State state; DInfoInterface* iface; O1* o1; O0SettingsStore* store; O1Requestor* requestor; }; FlickrTalker::FlickrTalker(QWidget* const parent, const QString& serviceName, DInfoInterface* const iface) : d(new Private) { d->parent = parent; d->serviceName = serviceName; d->iface = iface; m_photoSetsList = nullptr; m_authProgressDlg = nullptr; if (d->serviceName == QLatin1String("23")) { d->apiUrl = QLatin1String("http://www.23hq.com/services/rest/"); d->authUrl = QLatin1String("http://www.23hq.com/services/auth/"); d->uploadUrl = QLatin1String("http://www.23hq.com/services/upload/"); // bshanks: do 23 and flickr really share API keys? or does 23 not need // one? d->apikey = QLatin1String("49d585bafa0758cb5c58ab67198bf632"); d->secret = QLatin1String("34b39925e6273ffd"); } else { d->apiUrl = QLatin1String("https://www.flickr.com/services/rest/"); d->authUrl = QLatin1String("https://www.flickr.com/services/oauth/authorize?perms=write"); d->tokenUrl = QLatin1String("https://www.flickr.com/services/oauth/request_token"); d->accessUrl = QLatin1String("https://www.flickr.com/services/oauth/access_token"); d->uploadUrl = QLatin1String("https://up.flickr.com/services/upload/"); d->apikey = QLatin1String("49d585bafa0758cb5c58ab67198bf632"); d->secret = QLatin1String("34b39925e6273ffd"); } d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); /* Initialize selected photo set as empty. */ m_selectedPhotoSet = FPhotoSet(); /* Initialize photo sets list. */ m_photoSetsList = new QLinkedList(); d->o1 = new O1(this); d->o1->setClientId(d->apikey); d->o1->setClientSecret(d->secret); d->o1->setAuthorizeUrl(QUrl(d->authUrl)); d->o1->setAccessTokenUrl(QUrl(d->accessUrl)); d->o1->setRequestTokenUrl(QUrl(d->tokenUrl)); d->settings = WSToolUtils::getOauthSettings(this); d->store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this); d->store->setGroupKey(d->serviceName); d->o1->setStore(d->store); connect(d->o1, SIGNAL(linkingFailed()), this, SLOT(slotLinkingFailed())); connect(d->o1, SIGNAL(linkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->o1, SIGNAL(openBrowser(QUrl)), this, SLOT(slotOpenBrowser(QUrl))); d->requestor = new O1Requestor(d->netMngr, d->o1, this); } FlickrTalker::~FlickrTalker() { if (d->reply) { d->reply->abort(); } WSToolUtils::removeTemporaryDir(d->serviceName.toLatin1().constData()); delete m_photoSetsList; delete d; } void FlickrTalker::link(const QString& userName) { emit signalBusy(true); if (userName.isEmpty()) { d->store->setGroupKey(d->serviceName); } else { d->store->setGroupKey(d->serviceName + userName); } d->o1->link(); } void FlickrTalker::unLink() { d->o1->unlink(); } void FlickrTalker::removeUserName(const QString& userName) { if (userName.startsWith(d->serviceName)) { d->settings->beginGroup(userName); d->settings->remove(QString()); d->settings->endGroup(); } } void FlickrTalker::slotLinkingFailed() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Flickr fail"; d->username = QString(); emit signalBusy(false); } void FlickrTalker::slotLinkingSucceeded() { if (!d->o1->linked()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Flickr ok"; d->username = QString(); return; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Flickr ok"; d->username = d->o1->extraTokens()[QLatin1String("username")].toString(); d->userId = d->o1->extraTokens()[QLatin1String("user_nsid")].toString(); if (d->store->groupKey() == d->serviceName) { d->settings->beginGroup(d->serviceName); QStringList keys = d->settings->allKeys(); d->settings->endGroup(); - foreach(const QString& key, keys) + foreach (const QString& key, keys) { d->settings->beginGroup(d->serviceName); QVariant value = d->settings->value(key); d->settings->endGroup(); d->settings->beginGroup(d->serviceName + d->username); d->settings->setValue(key, value); d->settings->endGroup(); } d->store->setGroupKey(d->serviceName + d->username); removeUserName(d->serviceName); } emit signalLinkingSucceeded(); } void FlickrTalker::slotOpenBrowser(const QUrl& url) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser... (" << url << ")"; QDesktopServices::openUrl(url); } QString FlickrTalker::getMaxAllowedFileSize() { return d->maxSize; } void FlickrTalker::maxAllowedFileSize() { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.people.getLimits"); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_GETMAXSIZE; m_authProgressDlg->setLabelText(i18n("Getting the maximum allowed file size.")); m_authProgressDlg->setMaximum(4); m_authProgressDlg->setValue(1); emit signalBusy(true); } void FlickrTalker::listPhotoSets() { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List photoset invoked"; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.getList"); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_LISTPHOTOSETS; emit signalBusy(true); } void FlickrTalker::getPhotoProperty(const QString& method, const QStringList& argList) { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", method.toLatin1()); for (QStringList::const_iterator it = argList.constBegin(); it != argList.constEnd(); ++it) { QStringList str = (*it).split(QLatin1Char('='), QString::SkipEmptyParts); reqParams << O0RequestParameter(str[0].toLatin1(), str[1].toLatin1()); } QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_GETPHOTOPROPERTY; emit signalBusy(true); } void FlickrTalker::listPhotos(const QString& /*albumName*/) { // TODO } void FlickrTalker::createPhotoSet(const QString& /*albumName*/, const QString& albumTitle, const QString& albumDescription, const QString& primaryPhotoId) { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Create photoset invoked"; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.create"); reqParams << O0RequestParameter("title", albumTitle.toLatin1()); reqParams << O0RequestParameter("description", albumDescription.toLatin1()); reqParams << O0RequestParameter("primary_photo_id", primaryPhotoId.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_CREATEPHOTOSET; emit signalBusy(true); } void FlickrTalker::addPhotoToPhotoSet(const QString& photoId, const QString& photoSetId) { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "AddPhotoToPhotoSet invoked"; /* If the photoset id starts with the special string "UNDEFINED_", it means * it doesn't exist yet on Flickr and needs to be created. Note that it's * not necessary to subsequently add the photo to the photo set, as this * is done in the set creation call to Flickr. */ if (photoSetId.startsWith(QLatin1String("UNDEFINED_"))) { createPhotoSet(QLatin1String(""), m_selectedPhotoSet.title, m_selectedPhotoSet.description, photoId); } else { QUrl url(d->apiUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); reqParams << O0RequestParameter("method", "flickr.photosets.addPhoto"); reqParams << O0RequestParameter("photoset_id", photoSetId.toLatin1()); reqParams << O0RequestParameter("photo_id", photoId.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_ADDPHOTOTOPHOTOSET; emit signalBusy(true); } } bool FlickrTalker::addPhoto(const QString& photoPath, const FPhotoInfo& info, bool original, bool rescale, int maxDim, int imageQuality) { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return false; emit signalBusy(true); QUrl url(d->uploadUrl); QNetworkRequest netRequest(url); QList reqParams = QList(); QString path = photoPath; FlickrMPForm form; QString ispublic = (info.is_public == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_public"), ispublic, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_public", ispublic.toLatin1()); QString isfamily = (info.is_family == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_family"), isfamily, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_family", isfamily.toLatin1()); QString isfriend = (info.is_friend == 1) ? QLatin1String("1") : QLatin1String("0"); form.addPair(QLatin1String("is_friend"), isfriend, QLatin1String("text/plain")); reqParams << O0RequestParameter("is_friend", isfriend.toLatin1()); QString safetyLevel = QString::number(static_cast(info.safety_level)); form.addPair(QLatin1String("safety_level"), safetyLevel, QLatin1String("text/plain")); reqParams << O0RequestParameter("safety_level", safetyLevel.toLatin1()); QString contentType = QString::number(static_cast(info.content_type)); form.addPair(QLatin1String("content_type"), contentType, QLatin1String("text/plain")); reqParams << O0RequestParameter("content_type", contentType.toLatin1()); QString tags = QLatin1Char('"') + info.tags.join(QLatin1String("\" \"")) + QLatin1Char('"'); if (tags.length() > 0) { form.addPair(QLatin1String("tags"), tags, QLatin1String("text/plain")); reqParams << O0RequestParameter("tags", tags.toUtf8()); } if (!info.title.isEmpty()) { form.addPair(QLatin1String("title"), info.title, QLatin1String("text/plain")); reqParams << O0RequestParameter("title", info.title.toUtf8()); } if (!info.description.isEmpty()) { form.addPair(QLatin1String("description"), info.description, QLatin1String("text/plain")); reqParams << O0RequestParameter("description", info.description.toUtf8()); } if (!original) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage(); if (image.isNull()) { image.load(photoPath); } if (!image.isNull()) { if (!d->lastTmpFile.isEmpty()) { QFile::remove(d->lastTmpFile); } path = WSToolUtils::makeTemporaryDir(d->serviceName.toLatin1().constData()).filePath(QFileInfo(photoPath) .baseName().trimmed() + QLatin1String(".jpg")); if (rescale && (image.width() > maxDim || image.height() > maxDim)) { image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } image.save(path, "JPEG", imageQuality); d->lastTmpFile = path; // Restore all metadata. DMetadata meta; if (meta.load(photoPath)) { meta.setItemDimensions(image.size()); meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); // NOTE: see bug #153207: Flickr use IPTC keywords to create Tags in web interface // As IPTC do not support UTF-8, we need to remove it. // This function call remove all Application2 Tags. meta.removeIptcTags(QStringList() << QLatin1String("Application2")); // NOTE: see bug # 384260: Flickr use Xmp.dc.subject to create Tags // in web interface, we need to remove it. // This function call remove all Dublin Core Tags. meta.removeXmpTags(QStringList() << QLatin1String("dc")); meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); meta.save(path, true); } else { qCWarning(DIGIKAM_WEBSERVICES_LOG) << "Flickr::Image do not have metadata"; } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing and saving to temp file: " << path; } } QFileInfo tempFileInfo(path); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "QUrl path is " << QUrl::fromLocalFile(path) << "Image size (in bytes) is "<< tempFileInfo.size(); if (tempFileInfo.size() > (getMaxAllowedFileSize().toLongLong())) { emit signalAddPhotoFailed(i18n("File Size exceeds maximum allowed file size.")); emit signalBusy(false); return false; } if (!form.addFile(QLatin1String("photo"), path)) { emit signalBusy(false); return false; } form.finish(); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType()); d->reply = d->requestor->post(netRequest, reqParams, form.formData()); d->state = FE_ADDPHOTO; return true; } void FlickrTalker::setGeoLocation(const QString& photoId, const QString& lat, const QString& lon) { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (!d->o1->linked()) return; QUrl url(d->apiUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM)); QList reqParams = QList(); reqParams << O0RequestParameter("method", "flickr.photos.geo.setLocation"); reqParams << O0RequestParameter("photo_id", photoId.toLatin1()); reqParams << O0RequestParameter("lat", lat.toLatin1()); reqParams << O0RequestParameter("lon", lon.toLatin1()); QByteArray postData = O1::createQueryParameters(reqParams); d->reply = d->requestor->post(netRequest, reqParams, postData); d->state = FE_SETGEO; emit signalBusy(true); } QString FlickrTalker::getUserName() const { return d->username; } QString FlickrTalker::getUserId() const { return d->userId; } void FlickrTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (m_authProgressDlg && !m_authProgressDlg->isHidden()) { m_authProgressDlg->hide(); } } void FlickrTalker::slotError(const QString& error) { QString transError; int errorNo = error.toInt(); switch (errorNo) { case 2: transError = i18n("No photo specified"); break; case 3: transError = i18n("General upload failure"); break; case 4: transError = i18n("Filesize was zero"); break; case 5: transError = i18n("Filetype was not recognized"); break; case 6: transError = i18n("User exceeded upload limit"); break; case 96: transError = i18n("Invalid signature"); break; case 97: transError = i18n("Missing signature"); break; case 98: transError = i18n("Login Failed / Invalid auth token"); break; case 100: transError = i18n("Invalid API Key"); break; case 105: transError = i18n("Service currently unavailable"); break; case 108: transError = i18n("Invalid Frob"); break; case 111: transError = i18n("Format \"xxx\" not found"); break; case 112: transError = i18n("Method \"xxx\" not found"); break; case 114: transError = i18n("Invalid SOAP envelope"); break; case 115: transError = i18n("Invalid XML-RPC Method Call"); break; case 116: transError = i18n("The POST method is now required for all setters"); break; default: transError = i18n("Unknown error"); break; }; QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("Error Occurred: %1\nCannot proceed any further.", transError)); } void FlickrTalker::slotFinished(QNetworkReply* reply) { emit signalBusy(false); if (reply != d->reply) { return; } d->reply = nullptr; if (reply->error() != QNetworkReply::NoError) { if (d->state == FE_ADDPHOTO) { emit signalAddPhotoFailed(reply->errorString()); } else { QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), reply->errorString()); } reply->deleteLater(); return; } QByteArray buffer = reply->readAll(); switch (d->state) { case (FE_LOGIN): //parseResponseLogin(buffer); break; case (FE_LISTPHOTOSETS): parseResponseListPhotoSets(buffer); break; case (FE_LISTPHOTOS): parseResponseListPhotos(buffer); break; case (FE_GETPHOTOPROPERTY): parseResponsePhotoProperty(buffer); break; case (FE_ADDPHOTO): parseResponseAddPhoto(buffer); break; case (FE_ADDPHOTOTOPHOTOSET): parseResponseAddPhotoToPhotoSet(buffer); break; case (FE_CREATEPHOTOSET): parseResponseCreatePhotoSet(buffer); break; case (FE_GETMAXSIZE): parseResponseMaxSize(buffer); break; case (FE_SETGEO): parseResponseSetGeoLocation(buffer); break; default: // FR_LOGOUT break; } reply->deleteLater(); } void FlickrTalker::parseResponseMaxSize(const QByteArray& data) { QString errorString; QDomDocument doc(QLatin1String("mydocument")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("person")) { e = node.toElement(); QDomNode details = e.firstChild(); while (!details.isNull()) { if (details.isElement()) { e = details.toElement(); if (details.nodeName() == QLatin1String("videos")) { QDomAttr a = e.attributeNode(QLatin1String("maxupload")); d->maxSize = a.value(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Max upload size is"<maxSize; } } details = details.nextSibling(); } } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; errorString = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << errorString; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); } node = node.nextSibling(); } m_authProgressDlg->hide(); } void FlickrTalker::parseResponseCreatePhotoSet(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Parse response create photoset received " << data; //bool success = false; QDomDocument doc(QLatin1String("getListPhotoSets")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoset")) { // Parse the id from the response. QString new_id = node.toElement().attribute(QLatin1String("id")); // Set the new id in the photo sets list. QLinkedList::iterator it = m_photoSetsList->begin(); while (it != m_photoSetsList->end()) { if (it->id == m_selectedPhotoSet.id) { it->id = new_id; break; } ++it; } // Set the new id in the selected photo set. m_selectedPhotoSet.id = new_id; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "PhotoSet created successfully with id" << new_id; emit signalAddPhotoSetSucceeded(); } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; QString msg = node.toElement().attribute(QLatin1String("msg")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << msg; QMessageBox::critical(QApplication::activeWindow(), i18n("Error"), i18n("PhotoSet creation failed: ") + msg); } node = node.nextSibling(); } } void FlickrTalker::parseResponseListPhotoSets(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotosets" << data; bool success = false; QDomDocument doc(QLatin1String("getListPhotoSets")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; QString photoSet_id, photoSet_title, photoSet_description; m_photoSetsList->clear(); while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photosets")) { e = node.toElement(); QDomNode details = e.firstChild(); FPhotoSet fps; QDomNode detailsNode = details; while (!detailsNode.isNull()) { if (detailsNode.isElement()) { e = detailsNode.toElement(); if (detailsNode.nodeName() == QLatin1String("photoset")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "id=" << e.attribute(QLatin1String("id")); photoSet_id = e.attribute(QLatin1String("id")); // this is what is obtained from data. fps.id = photoSet_id; QDomNode photoSetDetails = detailsNode.firstChild(); QDomElement e_detail; while (!photoSetDetails.isNull()) { e_detail = photoSetDetails.toElement(); if (photoSetDetails.nodeName() == QLatin1String("title")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Title=" << e_detail.text(); photoSet_title = e_detail.text(); fps.title = photoSet_title; } else if (photoSetDetails.nodeName() == QLatin1String("description")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Description =" << e_detail.text(); photoSet_description = e_detail.text(); fps.description = photoSet_description; } photoSetDetails = photoSetDetails.nextSibling(); } m_photoSetsList->append(fps); } } detailsNode = detailsNode.nextSibling(); } details = details.nextSibling(); success = true; } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetPhotoList finished"; if (!success) { emit signalListPhotoSetsFailed(i18n("Failed to fetch list of photo sets.")); } else { emit signalListPhotoSetsSucceeded(); maxAllowedFileSize(); } } void FlickrTalker::parseResponseListPhotos(const QByteArray& data) { QDomDocument doc(QLatin1String("getPhotosList")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); //QDomElement e; //TODO } void FlickrTalker::parseResponseCreateAlbum(const QByteArray& data) { QDomDocument doc(QLatin1String("getCreateAlbum")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); //TODO } void FlickrTalker::parseResponseAddPhoto(const QByteArray& data) { bool success = false; QString line; QDomDocument doc(QLatin1String("AddPhoto Response")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; QString photoId; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoid")) { e = node.toElement(); // try to convert the node to an element. QDomNode details = e.firstChild(); photoId = e.text(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photoid= " << photoId; success = true; } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } if (!success) { emit signalAddPhotoFailed(i18n("Failed to upload photo")); } else { QString photoSetId = m_selectedPhotoSet.id; if (photoSetId == QLatin1String("-1")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "PhotoSet Id not set, not adding the photo to any photoset"; emit signalAddPhotoSucceeded(photoId); } else { addPhotoToPhotoSet(photoId, photoSetId); } } } void FlickrTalker::parseResponsePhotoProperty(const QByteArray& data) { bool success = false; QString line; QDomDocument doc(QLatin1String("Photos Properties")); if (!doc.setContent(data)) { return; } QDomElement docElem = doc.documentElement(); QDomNode node = docElem.firstChild(); QDomElement e; while (!node.isNull()) { if (node.isElement() && node.nodeName() == QLatin1String("photoid")) { e = node.toElement(); // try to convert the node to an element. QDomNode details = e.firstChild(); success = true; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photoid=" << e.text(); } if (node.isElement() && node.nodeName() == QLatin1String("err")) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking Error in response"; QString code = node.toElement().attribute(QLatin1String("code")); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Error code=" << code; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Msg=" << node.toElement().attribute(QLatin1String("msg")); emit signalError(code); } node = node.nextSibling(); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetToken finished"; if (!success) { emit signalAddPhotoFailed(i18n("Failed to query photo information")); } else { emit signalAddPhotoSucceeded(QLatin1String("")); } } void FlickrTalker::parseResponseAddPhotoToPhotoSet(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotosets" << data; emit signalAddPhotoSucceeded(QLatin1String("")); } void FlickrTalker::parseResponseSetGeoLocation(const QByteArray& data) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseSetGeoLocation" << data; emit signalAddPhotoSucceeded(QLatin1String("")); } } // namespace DigikamGenericFlickrPlugin diff --git a/core/dplugins/generic/webservices/flickr/flickrwindow.cpp b/core/dplugins/generic/webservices/flickr/flickrwindow.cpp index 5aa74581c9..b9d85e336b 100644 --- a/core/dplugins/generic/webservices/flickr/flickrwindow.cpp +++ b/core/dplugins/generic/webservices/flickr/flickrwindow.cpp @@ -1,889 +1,889 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-17-06 * Description : a tool to export images to Flickr web service * * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * * 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, 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. * * ============================================================ */ #include "flickrwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dprogresswdg.h" #include "flickrtalker.h" #include "flickritem.h" #include "flickrlist.h" #include "wsselectuserdlg.h" #include "digikam_debug.h" #include "flickrnewalbumdlg.h" #include "previewloadthread.h" #include "flickrwidget_p.h" namespace DigikamGenericFlickrPlugin { class Q_DECL_HIDDEN FlickrWindow::Private { public: explicit Private() { uploadCount = 0; uploadTotal = 0; newAlbumBtn = nullptr; changeUserButton = nullptr; removeAccount = nullptr; albumsListComboBox = nullptr; publicCheckBox = nullptr; familyCheckBox = nullptr; friendsCheckBox = nullptr; exportHostTagsCheckBox = nullptr; stripSpaceTagsCheckBox = nullptr; addExtraTagsCheckBox = nullptr; originalCheckBox = nullptr; resizeCheckBox = nullptr; dimensionSpinBox = nullptr; imageQualitySpinBox = nullptr; extendedPublicationButton = nullptr; extendedTagsButton = nullptr; contentTypeComboBox = nullptr; safetyLevelComboBox = nullptr; userNameDisplayLabel = nullptr; authProgressDlg = nullptr; tagsLineEdit = nullptr; widget = nullptr; talker = nullptr; imglst = nullptr; select = nullptr; albumDlg = nullptr; iface = nullptr; } unsigned int uploadCount; unsigned int uploadTotal; QString serviceName; QPushButton* newAlbumBtn; QPushButton* changeUserButton; QPushButton* removeAccount; QComboBox* albumsListComboBox; QCheckBox* publicCheckBox; QCheckBox* familyCheckBox; QCheckBox* friendsCheckBox; QCheckBox* exportHostTagsCheckBox; QCheckBox* stripSpaceTagsCheckBox; QCheckBox* addExtraTagsCheckBox; QCheckBox* originalCheckBox; QCheckBox* resizeCheckBox; QSpinBox* dimensionSpinBox; QSpinBox* imageQualitySpinBox; QPushButton* extendedPublicationButton; QPushButton* extendedTagsButton; WSComboBoxIntermediate* contentTypeComboBox; WSComboBoxIntermediate* safetyLevelComboBox; QString username; QString userId; QString lastSelectedAlbum; QLabel* userNameDisplayLabel; QProgressDialog* authProgressDlg; QList< QPair > uploadQueue; QLineEdit* tagsLineEdit; FlickrWidget* widget; FlickrTalker* talker; FlickrList* imglst; WSSelectUserDlg* select; FlickrNewAlbumDlg* albumDlg; DInfoInterface* iface; }; FlickrWindow::FlickrWindow(DInfoInterface* const iface, QWidget* const /*parent*/, const QString& serviceName) : WSToolDialog(nullptr, QString::fromLatin1("%1Export Dialog").arg(serviceName)), d(new Private) { d->iface = iface; d->serviceName = serviceName; setWindowTitle(i18n("Export to %1 Web Service", d->serviceName)); setModal(false); KConfig config; KConfigGroup grp = config.group(QString::fromLatin1("%1Export Settings").arg(d->serviceName)); if (grp.exists()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString::fromLatin1("%1Export Settings").arg(d->serviceName) << " exists, deleting it"; grp.deleteGroup(); } d->select = new WSSelectUserDlg(nullptr, serviceName); d->uploadCount = 0; d->uploadTotal = 0; d->widget = new FlickrWidget(this, iface, serviceName); d->albumDlg = new FlickrNewAlbumDlg(this, QLatin1String("Flickr")); d->albumsListComboBox = d->widget->getAlbumsCoB(); d->newAlbumBtn = d->widget->getNewAlbmBtn(); d->originalCheckBox = d->widget->getOriginalCheckBox(); d->resizeCheckBox = d->widget->getResizeCheckBox(); d->publicCheckBox = d->widget->d->publicCheckBox; d->familyCheckBox = d->widget->d->familyCheckBox; d->friendsCheckBox = d->widget->d->friendsCheckBox; d->dimensionSpinBox = d->widget->getDimensionSpB(); d->imageQualitySpinBox = d->widget->getImgQualitySpB(); d->extendedTagsButton = d->widget->d->extendedTagsButton; d->addExtraTagsCheckBox = d->widget->d->addExtraTagsCheckBox; d->extendedPublicationButton = d->widget->d->extendedPublicationButton; d->safetyLevelComboBox = d->widget->d->safetyLevelComboBox; d->contentTypeComboBox = d->widget->d->contentTypeComboBox; d->tagsLineEdit = d->widget->d->tagsLineEdit; d->exportHostTagsCheckBox = d->widget->d->exportHostTagsCheckBox; d->stripSpaceTagsCheckBox = d->widget->d->stripSpaceTagsCheckBox; d->changeUserButton = d->widget->getChangeUserBtn(); d->removeAccount = d->widget->d->removeAccount; d->userNameDisplayLabel = d->widget->getUserNameLabel(); d->imglst = d->widget->d->imglst; startButton()->setText(i18n("Start Uploading")); startButton()->setToolTip(QString()); setMainWidget(d->widget); d->widget->setMinimumSize(800, 600); connect(d->imglst, SIGNAL(signalImageListChanged()), this, SLOT(slotImageListChanged())); // -------------------------------------------------------------------------- d->talker = new FlickrTalker(this, serviceName, d->iface); connect(d->talker, SIGNAL(signalError(QString)), d->talker, SLOT(slotError(QString))); connect(d->talker, SIGNAL(signalBusy(bool)), this, SLOT(slotBusy(bool))); connect(d->talker, SIGNAL(signalAddPhotoSucceeded(QString)), this, SLOT(slotAddPhotoSucceeded(QString))); connect(d->talker, SIGNAL(signalAddPhotoFailed(QString)), this, SLOT(slotAddPhotoFailed(QString))); connect(d->talker, SIGNAL(signalAddPhotoSetSucceeded()), this, SLOT(slotAddPhotoSetSucceeded())); connect(d->talker, SIGNAL(signalListPhotoSetsSucceeded()), this, SLOT(slotPopulatePhotoSetComboBox())); connect(d->talker, SIGNAL(signalListPhotoSetsFailed(QString)), this, SLOT(slotListPhotoSetsFailed(QString))); connect(d->talker, SIGNAL(signalLinkingSucceeded()), this, SLOT(slotLinkingSucceeded())); connect(d->widget->progressBar(), SIGNAL(signalProgressCanceled()), this, SLOT(slotAddPhotoCancelAndClose())); connect(d->widget->getReloadBtn(), SIGNAL(clicked()), this, SLOT(slotReloadPhotoSetRequest())); // -------------------------------------------------------------------------- connect(d->changeUserButton, SIGNAL(clicked()), this, SLOT(slotUserChangeRequest())); connect(d->removeAccount, SIGNAL(clicked()), this, SLOT(slotRemoveAccount())); connect(d->newAlbumBtn, SIGNAL(clicked()), this, SLOT(slotCreateNewPhotoSet())); // -------------------------------------------------------------------------- d->authProgressDlg = new QProgressDialog(this); d->authProgressDlg->setModal(true); d->authProgressDlg->setAutoReset(true); d->authProgressDlg->setAutoClose(true); d->authProgressDlg->setMaximum(0); d->authProgressDlg->reset(); connect(d->authProgressDlg, SIGNAL(canceled()), this, SLOT(slotAuthCancel())); d->talker->m_authProgressDlg = d->authProgressDlg; // -------------------------------------------------------------------------- connect(this, &QDialog::finished, this, &FlickrWindow::slotFinished); connect(this, SIGNAL(cancelClicked()), this, SLOT(slotCancelClicked())); connect(startButton(), &QPushButton::clicked, this, &FlickrWindow::slotUser1); d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } FlickrWindow::~FlickrWindow() { delete d->select; delete d->authProgressDlg; delete d->talker; delete d->widget; delete d; } void FlickrWindow::setItemsList(const QList& urls) { d->widget->imagesList()->slotAddImages(urls); } void FlickrWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } void FlickrWindow::slotFinished() { writeSettings(); d->imglst->listView()->clear(); } void FlickrWindow::setUiInProgressState(bool inProgress) { setRejectButtonMode(inProgress ? QDialogButtonBox::Cancel : QDialogButtonBox::Close); if (inProgress) { d->widget->progressBar()->show(); } else { d->widget->progressBar()->hide(); d->widget->progressBar()->progressCompleted(); } } void FlickrWindow::slotCancelClicked() { d->talker->cancel(); d->uploadQueue.clear(); setUiInProgressState(false); } void FlickrWindow::slotAddPhotoCancelAndClose() { writeSettings(); d->imglst->listView()->clear(); d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); d->talker->cancel(); reject(); } void FlickrWindow::reactivate() { d->userNameDisplayLabel->setText(QString()); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); d->widget->d->imglst->loadImagesFromCurrentSelection(); show(); } void FlickrWindow::readSettings(QString uname) { KConfig config; QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, uname); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; KConfigGroup grp = config.group(groupName); d->exportHostTagsCheckBox->setChecked(grp.readEntry("Export Host Tags", false)); d->extendedTagsButton->setChecked(grp.readEntry("Show Extended Tag Options", false)); d->addExtraTagsCheckBox->setChecked(grp.readEntry("Add Extra Tags", false)); d->stripSpaceTagsCheckBox->setChecked(grp.readEntry("Strip Space From Tags", false)); d->publicCheckBox->setChecked(grp.readEntry("Public Sharing", false)); d->familyCheckBox->setChecked(grp.readEntry("Family Sharing", false)); d->friendsCheckBox->setChecked(grp.readEntry("Friends Sharing", false)); d->extendedPublicationButton->setChecked(grp.readEntry("Show Extended Publication Options", false)); int safetyLevel = d->safetyLevelComboBox->findData(QVariant(grp.readEntry("Safety Level", 0))); if (safetyLevel == -1) { safetyLevel = 0; } d->safetyLevelComboBox->setCurrentIndex(safetyLevel); int contentType = d->contentTypeComboBox->findData(QVariant(grp.readEntry("Content Type", 0))); if (contentType == -1) { contentType = 0; } d->contentTypeComboBox->setCurrentIndex(contentType); d->originalCheckBox->setChecked(grp.readEntry("Upload Original", false)); d->resizeCheckBox->setChecked(grp.readEntry("Resize", false)); d->dimensionSpinBox->setValue(grp.readEntry("Maximum Width", 1600)); d->imageQualitySpinBox->setValue(grp.readEntry("Image Quality", 85)); winId(); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::restoreWindowSize(windowHandle(), dialogGroup); resize(windowHandle()->size()); } void FlickrWindow::writeSettings() { KConfig config; QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Group name is:" << groupName; if (QString::compare(QString::fromLatin1("%1Export Settings").arg(d->serviceName), groupName) == 0) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Not writing entry of group" << groupName; return; } KConfigGroup grp = config.group(groupName); grp.writeEntry("username", d->username); grp.writeEntry("Export Host Tags", d->exportHostTagsCheckBox->isChecked()); grp.writeEntry("Show Extended Tag Options", d->extendedTagsButton->isChecked()); grp.writeEntry("Add Extra Tags", d->addExtraTagsCheckBox->isChecked()); grp.writeEntry("Strip Space From Tags", d->stripSpaceTagsCheckBox->isChecked()); grp.writeEntry("Public Sharing", d->publicCheckBox->isChecked()); grp.writeEntry("Family Sharing", d->familyCheckBox->isChecked()); grp.writeEntry("Friends Sharing", d->friendsCheckBox->isChecked()); grp.writeEntry("Show Extended Publication Options", d->extendedPublicationButton->isChecked()); int safetyLevel = d->safetyLevelComboBox->itemData(d->safetyLevelComboBox->currentIndex()).toInt(); grp.writeEntry("Safety Level", safetyLevel); int contentType = d->contentTypeComboBox->itemData(d->contentTypeComboBox->currentIndex()).toInt(); grp.writeEntry("Content Type", contentType); grp.writeEntry("Resize", d->resizeCheckBox->isChecked()); grp.writeEntry("Upload Original", d->originalCheckBox->isChecked()); grp.writeEntry("Maximum Width", d->dimensionSpinBox->value()); grp.writeEntry("Image Quality", d->imageQualitySpinBox->value()); KConfigGroup dialogGroup = config.group(QString::fromLatin1("%1Export Dialog").arg(d->serviceName)); KWindowConfig::saveWindowSize(windowHandle(), dialogGroup); config.sync(); } void FlickrWindow::slotLinkingSucceeded() { d->username = d->talker->getUserName(); d->userId = d->talker->getUserId(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotLinkingSucceeded invoked setting user Display name to" << d->username; d->userNameDisplayLabel->setText(QString::fromLatin1("%1").arg(d->username)); KConfig config; - foreach(const QString& group, config.groupList()) + foreach (const QString& group, config.groupList()) { if (!(group.contains(d->serviceName))) continue; KConfigGroup grp = config.group(group); if (group.contains(d->username)) { readSettings(d->username); break; } } writeSettings(); d->talker->listPhotoSets(); } void FlickrWindow::slotBusy(bool val) { if (val) { setCursor(Qt::WaitCursor); } else { setCursor(Qt::ArrowCursor); } } void FlickrWindow::slotError(const QString& msg) { QMessageBox::critical(this, i18n("Error"), msg); } void FlickrWindow::slotUserChangeRequest() { writeSettings(); d->userNameDisplayLabel->setText(QString()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot Change User Request"; d->select->reactivate(); readSettings(d->select->getUserName()); d->talker->link(d->select->getUserName()); } void FlickrWindow::slotRemoveAccount() { KConfig config; QString groupName = QString::fromLatin1("%1%2Export Settings").arg(d->serviceName, d->username); KConfigGroup grp = config.group(groupName); if (grp.exists()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Removing Account having group" << groupName; grp.deleteGroup(); } d->talker->unLink(); d->talker->removeUserName(d->serviceName + d->username); d->userNameDisplayLabel->setText(QString()); d->username = QString(); } /** * Try to guess a sensible set name from the urls given. * Currently, it extracs the last path name component, and returns the most * frequently seen. The function could be expanded to, for example, only * accept the path if it occurs at least 50% of the time. It could also look * further up in the path name. */ QString FlickrWindow::guessSensibleSetName(const QList& urlList) const { QMap nrFolderOccurences; // Extract last component of directory - foreach(const QUrl& url, urlList) + foreach (const QUrl& url, urlList) { QString dir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); QStringList list = dir.split(QLatin1Char('/')); if (list.isEmpty()) continue; nrFolderOccurences[list.last()]++; } int maxCount = 0; int totalCount = 0; QString name; for (QMap::const_iterator it = nrFolderOccurences.constBegin(); it != nrFolderOccurences.constEnd() ; ++it) { totalCount += it.value(); if (it.value() > maxCount) { maxCount = it.value(); name = it.key(); } } // If there is only one entry or one name appears at least twice, return the suggestion if (totalCount == 1 || maxCount > 1) return name; return QString(); } /** This method is called when the photo set creation button is pressed. It * summons a creation dialog for user input. When that is closed, it * creates a new photo set in the local list. The id gets the form of * UNDEFINED_ followed by a number, to indicate that it doesn't exist on * Flickr yet. */ void FlickrWindow::slotCreateNewPhotoSet() { if (d->albumDlg->exec() == QDialog::Accepted) { FPhotoSet fps; d->albumDlg->getFolderProperties(fps); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "in slotCreateNewPhotoSet()" << fps.title; // Lets find an UNDEFINED_ style id that isn't taken yet.s QString id; int i = 0; id = QLatin1String("UNDEFINED_") + QString::number(i); QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { FPhotoSet fps = *it; if (fps.id == id) { id = QLatin1String("UNDEFINED_") + QString::number(++i); it = d->talker->m_photoSetsList->begin(); } ++it; } fps.id = id; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Created new photoset with temporary id" << id; // Append the new photoset to the list. d->talker->m_photoSetsList->prepend(fps); d->talker->m_selectedPhotoSet = fps; // Re-populate the photo sets combo box. slotPopulatePhotoSetComboBox(); } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "New Photoset creation aborted"; } } void FlickrWindow::slotAuthCancel() { d->talker->cancel(); d->authProgressDlg->hide(); } void FlickrWindow::slotPopulatePhotoSetComboBox() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotPopulatePhotoSetComboBox invoked"; if (d->talker && d->talker->m_photoSetsList) { QLinkedList * const list = d->talker->m_photoSetsList; d->albumsListComboBox->clear(); d->albumsListComboBox->insertItem(0, i18n("Photostream Only")); d->albumsListComboBox->insertSeparator(1); QLinkedList::iterator it = list->begin(); int index = 2; int curr_index = 0; while (it != list->end()) { FPhotoSet photoSet = *it; QString name = photoSet.title; // Store the id as user data, because the title is not unique. QVariant id = QVariant(photoSet.id); if (id == d->talker->m_selectedPhotoSet.id) { curr_index = index; } d->albumsListComboBox->insertItem(index++, name, id); ++it; } d->albumsListComboBox->setCurrentIndex(curr_index); } } /** This slot is call when 'Start Uploading' button is pressed. */ void FlickrWindow::slotUser1() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages invoked"; //d->widget->d->tab->setCurrentIndex(FlickrWidget::FILELIST); if (d->imglst->imageUrls().isEmpty()) { return; } typedef QPair Pair; d->uploadQueue.clear(); for (int i = 0 ; i < d->imglst->listView()->topLevelItemCount() ; ++i) { FlickrListViewItem* const lvItem = dynamic_cast(d->imglst->listView()->topLevelItem(i)); if (lvItem) { DItemInfo info(d->iface->itemInfo(lvItem->url())); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Adding images" << lvItem->url() << " to the list"; FPhotoInfo temp; temp.title = info.title(); temp.description = info.comment(); temp.size = info.fileSize(); temp.is_public = lvItem->isPublic() ? 1 : 0; temp.is_family = lvItem->isFamily() ? 1 : 0; temp.is_friend = lvItem->isFriends() ? 1 : 0; temp.safety_level = lvItem->safetyLevel(); temp.content_type = lvItem->contentType(); QStringList tagsFromDialog = d->tagsLineEdit->text().split(QLatin1Char(','), QString::SkipEmptyParts); QStringList tagsFromList = lvItem->extraTags(); QStringList allTags; QStringList::Iterator itTags; // Tags from the dialog itTags = tagsFromDialog.begin(); while (itTags != tagsFromDialog.end()) { allTags.append(*itTags); ++itTags; } // Tags from the database if (d->exportHostTagsCheckBox->isChecked()) { QStringList tagsFromDatabase; tagsFromDatabase = info.keywords(); itTags = tagsFromDatabase.begin(); while (itTags != tagsFromDatabase.end()) { allTags.append(*itTags); ++itTags; } } // Tags from the list view. itTags = tagsFromList.begin(); while (itTags != tagsFromList.end()) { allTags.append(*itTags); ++itTags; } // Remove spaces if the user doesn't like them. if (d->stripSpaceTagsCheckBox->isChecked()) { for (QStringList::iterator it = allTags.begin(); it != allTags.end(); ++it) { *it = (*it).trimmed().remove(QLatin1Char(' ')); } } // Debug the tag list. itTags = allTags.begin(); while (itTags != allTags.end()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags list:" << (*itTags); ++itTags; } temp.tags = allTags; d->uploadQueue.append(Pair(lvItem->url(), temp)); } } d->uploadTotal = d->uploadQueue.count(); d->uploadCount = 0; d->widget->progressBar()->reset(); slotAddPhotoNext(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SlotUploadImages done"; } void FlickrWindow::slotAddPhotoNext() { if (d->uploadQueue.isEmpty()) { d->widget->progressBar()->reset(); setUiInProgressState(false); return; } typedef QPair Pair; Pair pathComments = d->uploadQueue.first(); FPhotoInfo info = pathComments.second; QString selectedPhotoSetId = d->albumsListComboBox->itemData(d->albumsListComboBox->currentIndex()).toString(); if (selectedPhotoSetId.isEmpty()) { d->talker->m_selectedPhotoSet = FPhotoSet(); } else { QLinkedList::iterator it = d->talker->m_photoSetsList->begin(); while (it != d->talker->m_photoSetsList->end()) { if (it->id == selectedPhotoSetId) { d->talker->m_selectedPhotoSet = *it; break; } ++it; } } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Max allowed file size is:" << d->talker->getMaxAllowedFileSize().toLongLong() << "File Size is" << info.size; bool res = d->talker->addPhoto(pathComments.first.toLocalFile(), //the file path info, d->originalCheckBox->isChecked(), d->resizeCheckBox->isChecked(), d->dimensionSpinBox->value(), d->imageQualitySpinBox->value()); if (!res) { slotAddPhotoFailed(QLatin1String("")); return; } if (d->widget->progressBar()->isHidden()) { setUiInProgressState(true); d->widget->progressBar()->progressScheduled(i18n("Flickr Export"), true, true); d->widget->progressBar()->progressThumbnailChanged( QIcon::fromTheme(QLatin1String("dk-flickr")).pixmap(22, 22)); } } void FlickrWindow::slotAddPhotoSucceeded(const QString& photoId) { QUrl photoUrl = d->uploadQueue.first().first; // Set location for uploaded photo DItemInfo info(d->iface->itemInfo(photoUrl)); if (info.hasGeolocationInfo() && !photoId.isEmpty()) { d->talker->setGeoLocation(photoId, QString::number(info.latitude()), QString::number(info.longitude())); return; } // Remove photo uploaded from the list d->imglst->removeItemByUrl(photoUrl); d->uploadQueue.removeFirst(); d->uploadCount++; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } void FlickrWindow::slotListPhotoSetsFailed(const QString& msg) { QMessageBox::critical(this, QLatin1String("Error"), i18n("Failed to Fetch Photoset information from %1. %2\n", d->serviceName, msg)); } void FlickrWindow::slotAddPhotoFailed(const QString& msg) { QPointer warn = new QMessageBox(QMessageBox::Warning, i18n("Warning"), i18n("Failed to upload photo into %1. %2\nDo you want to continue?", d->serviceName, msg), QMessageBox::Yes | QMessageBox::No); (warn->button(QMessageBox::Yes))->setText(i18n("Continue")); (warn->button(QMessageBox::No))->setText(i18n("Cancel")); if (warn->exec() != QMessageBox::Yes) { d->uploadQueue.clear(); d->widget->progressBar()->reset(); setUiInProgressState(false); } else { d->uploadQueue.removeFirst(); d->uploadTotal--; d->widget->progressBar()->setMaximum(d->uploadTotal); d->widget->progressBar()->setValue(d->uploadCount); slotAddPhotoNext(); } delete warn; } /* Method called when a photo set has been successfully created on Flickr. * It functions to restart the normal flow after a photo set has been created * on Flickr. */ void FlickrWindow::slotAddPhotoSetSucceeded() { slotPopulatePhotoSetComboBox(); slotAddPhotoSucceeded(QLatin1String("")); } void FlickrWindow::slotImageListChanged() { startButton()->setEnabled(!(d->widget->d->imglst->imageUrls().isEmpty())); } void FlickrWindow::slotReloadPhotoSetRequest() { d->talker->listPhotoSets(); } } // namespace DigikamGenericFlickrPlugin diff --git a/core/dplugins/generic/webservices/unified/manager/wsauthentication.cpp b/core/dplugins/generic/webservices/unified/manager/wsauthentication.cpp index 06aa4a2993..ccd3e80d8b 100644 --- a/core/dplugins/generic/webservices/unified/manager/wsauthentication.cpp +++ b/core/dplugins/generic/webservices/unified/manager/wsauthentication.cpp @@ -1,437 +1,437 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2018-07-03 * Description : Web Service authentication container. * * Copyright (C) 2018 by Thanh Trung Dinh * * 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, 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. * * ============================================================ */ #include "wsauthentication.h" // Qt includes #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "dmetadata.h" #include "previewloadthread.h" #include "wstoolutils.h" #include "wstalker.h" #include "dbtalker.h" #include "fbtalker.h" #include "fbnewalbumdlg.h" #include "flickrtalker.h" #include "gptalker.h" #include "gdtalker.h" #include "imgurtalker.h" #include "smugtalker.h" namespace DigikamGenericUnifiedPlugin { class Q_DECL_HIDDEN WSAuthentication::Private { public: explicit Private() : wizard(0), iface(0), talker(0), ws(WSSettings::WebService::FLICKR), albumDlg(0), imagesCount(0), imagesTotal(0) { } WSWizard* wizard; DInfoInterface* iface; WSTalker* talker; WSSettings::WebService ws; QString serviceName; WSNewAlbumDialog* albumDlg; QString currentAlbumId; WSAlbum baseAlbum; QStringList tmpPath; QString tmpDir; unsigned int imagesCount; unsigned int imagesTotal; QMap imagesCaption; QList transferQueue; }; WSAuthentication::WSAuthentication(QWidget* const parent, DInfoInterface* const iface) : d(new Private()) { d->wizard = dynamic_cast(parent); if (d->wizard) { d->iface = d->wizard->iface(); } else { d->iface = iface; } /* -------------------- * Temporary path to store images before uploading */ d->tmpPath.clear(); d->tmpDir = WSToolUtils::makeTemporaryDir(d->serviceName.toUtf8().data()).absolutePath() + QLatin1Char('/'); } WSAuthentication::~WSAuthentication() { slotCancel(); delete d; } void WSAuthentication::createTalker(WSSettings::WebService ws, const QString& serviceName) { d->ws = ws; d->serviceName = serviceName; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "create " << serviceName << "talker"; switch (ws) { case WSSettings::WebService::FLICKR: //d->talker = new DigikamGenericFlickrPlugin::FlickrTalker(d->wizard, serviceName, d->iface); break; case WSSettings::WebService::DROPBOX: //d->talker = new DigikamGenericDropBoxPlugin::DBTalker(d->wizard); break; case WSSettings::WebService::IMGUR: //d->talker = new DigikamGenericImgUrPlugin::ImgurTalker(d->wizard); break; case WSSettings::WebService::FACEBOOK: //d->albumDlg = new DigikamGenericFaceBookPlugin::FbNewAlbumDlg(d->wizard, d->serviceName); //d->talker = new DigikamGenericFaceBookPlugin::FbTalker(d->wizard, d->albumDlg); break; case WSSettings::WebService::SMUGMUG: //d->talker = new DigikamGenericSmugMugPlugin::SmugTalker(d->iface, d->wizard); break; case WSSettings::WebService::GDRIVE: //d->talker = new DigikamGenericGoogleServicesPlugin::GDTalker(d->wizard); break; case WSSettings::WebService::GPHOTO: //d->talker = new DigikamGenericGoogleServicesPlugin::GPTalker(d->wizard); break; } connect(d->talker, SIGNAL(signalOpenBrowser(QUrl)), this, SIGNAL(signalOpenBrowser(QUrl))); connect(d->talker, SIGNAL(signalCloseBrowser()), this, SIGNAL(signalCloseBrowser())); connect(d->talker, SIGNAL(signalAuthenticationComplete(bool)), this, SIGNAL(signalAuthenticationComplete(bool))); connect(this, SIGNAL(signalResponseTokenReceived(QMap)), d->talker, SLOT(slotResponseTokenReceived(QMap))); connect(d->talker, SIGNAL(signalCreateAlbumDone(int,QString,QString)), this, SIGNAL(signalCreateAlbumDone(int,QString,QString))); connect(d->talker, SIGNAL(signalListAlbumsDone(int,QString,QList)), this, SLOT(slotListAlbumsDone(int,QString,QList))); connect(d->talker, SIGNAL(signalAddPhotoDone(int,QString)), this, SLOT(slotAddPhotoDone(int,QString))); } void WSAuthentication::cancelTalker() { if (d->talker) { d->talker->cancel(); } } QString WSAuthentication::webserviceName() { return d->serviceName; } void WSAuthentication::authenticate() { d->talker->authenticate(); } void WSAuthentication::reauthenticate() { d->talker->reauthenticate(); } bool WSAuthentication::authenticated() const { return d->talker->linked(); } void WSAuthentication::parseTreeFromListAlbums(const QList & albumsList, QMap& albumTree, QStringList& rootAlbums) { foreach (const WSAlbum& album, albumsList) { if (albumTree.contains(album.id)) { albumTree[album.id].title = album.title; albumTree[album.id].uploadable = album.uploadable; } else { AlbumSimplified item(album.title, album.uploadable); albumTree[album.id] = item; } if (album.isRoot) { rootAlbums << album.id; } else { if (albumTree.contains(album.parentID)) { albumTree[album.parentID].childrenIDs << album.id; } else { AlbumSimplified parentAlbum; parentAlbum.childrenIDs << album.id; albumTree[album.parentID] = parentAlbum; } } } } QString WSAuthentication::getImageCaption(const QString& fileName) { DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(fileName))); // If webservice doesn't support image titles, include it in descriptions if needed. QStringList descriptions = QStringList() << info.title() << info.comment(); descriptions.removeAll(QLatin1String("")); return descriptions.join(QLatin1String("\n\n")); } void WSAuthentication::prepareForUpload() { d->transferQueue = d->wizard->settings()->inputImages; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "prepareForUpload invoked"; if (d->transferQueue.isEmpty()) { emit signalMessage(QLatin1String("transferQueue is empty"), true); return; } d->currentAlbumId = d->wizard->settings()->currentAlbumId; d->imagesTotal = d->transferQueue.count(); d->imagesCount = 0; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "upload request got album id from widget: " << d->currentAlbumId; if (d->wizard->settings()->imagesChangeProp) { - foreach(const QUrl& imgUrl, d->transferQueue) + foreach (const QUrl& imgUrl, d->transferQueue) { QString imgPath = imgUrl.toLocalFile(); QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage(); if (image.isNull()) { image.load(imgPath); } if (image.isNull()) { emit d->talker->signalAddPhotoDone(666, i18n("Cannot open image at %1\n", imgPath)); return; } // get temporary file name d->tmpPath << d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format(); // rescale image if requested int maxDim = d->wizard->settings()->imageSize; if (image.width() > maxDim || image.height() > maxDim) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim; image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Saving to temp file: " << d->tmpPath.last(); image.save(d->tmpPath.last(), "JPEG", d->wizard->settings()->imageCompression); // copy meta data to temporary image and get caption for image DMetadata meta; QString caption = QLatin1String(""); if (meta.load(imgPath)) { meta.setItemDimensions(image.size()); meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); meta.save(d->tmpPath.last(), true); caption = getImageCaption(imgPath); } d->imagesCaption[imgPath] = caption; } } } unsigned int WSAuthentication::numberItemsUpload() { return d->imagesTotal; } void WSAuthentication::uploadNextPhoto() { if (d->transferQueue.isEmpty()) { emit signalDone(); return; } /* * This comparison is a little bit complicated and may seem unnecessary, but it will be useful later * when we will be able to choose to change or not image properties for EACH image. */ QString imgPath = d->transferQueue.first().toLocalFile(); QString tmpPath = d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format(); if (!d->tmpPath.isEmpty() && tmpPath == d->tmpPath.first()) { d->talker->addPhoto(tmpPath, d->currentAlbumId, d->imagesCaption[imgPath]); d->tmpPath.removeFirst(); } else { d->talker->addPhoto(imgPath, d->currentAlbumId, d->imagesCaption[imgPath]); } } void WSAuthentication::startTransfer() { uploadNextPhoto(); } void WSAuthentication::slotCancel() { // First we cancel talker cancelTalker(); // Then the folder containing all temporary photos to upload will be removed after all. QDir tmpDir(d->tmpDir); if (tmpDir.exists()) { tmpDir.removeRecursively(); } emit signalProgress(0); } void WSAuthentication::slotNewAlbumRequest() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot create New Album"; if (d->albumDlg->exec() == QDialog::Accepted) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling New Album method"; d->talker->createNewAlbum(); } } void WSAuthentication::slotListAlbumsRequest() { d->talker->listAlbums(); } void WSAuthentication::slotListAlbumsDone(int errCode, const QString& errMsg, const QList& albumsList) { QString albumDebug = QLatin1String(""); foreach (const WSAlbum &album, albumsList) { albumDebug.append(QString::fromLatin1("%1: %2\n").arg(album.id).arg(album.title)); } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received albums (errCode = " << errCode << ", errMsg = " << errMsg << "): " << albumDebug; if (errCode != 0) { QMessageBox::critical(QApplication::activeWindow(), i18n("%1 Call Failed", d->serviceName), i18n("Code: %1. %2", errCode, errMsg)); return; } QMap albumTree; QStringList rootAlbums; parseTreeFromListAlbums(albumsList, albumTree, rootAlbums); emit signalListAlbumsDone(albumTree, rootAlbums, QLatin1String("")); } void WSAuthentication::slotAddPhotoDone(int errCode, const QString& errMsg) { if (errCode == 0) { emit signalMessage(QDir::toNativeSeparators(d->transferQueue.first().toLocalFile()), false); d->transferQueue.removeFirst(); d->imagesCount++; emit signalProgress(d->imagesCount); } else { if (QMessageBox::question(d->wizard, i18n("Uploading Failed"), i18n("Failed to upload photo: %1\n" "Do you want to continue?", errMsg)) != QMessageBox::Yes) { d->transferQueue.clear(); return; } } uploadNextPhoto(); } } // namespace DigikamGenericUnifiedPlugin diff --git a/core/dplugins/generic/webservices/unified/wizard/wsintropage.cpp b/core/dplugins/generic/webservices/unified/wizard/wsintropage.cpp index 3801280d90..6a84f9b131 100644 --- a/core/dplugins/generic/webservices/unified/wizard/wsintropage.cpp +++ b/core/dplugins/generic/webservices/unified/wizard/wsintropage.cpp @@ -1,238 +1,238 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-27 * Description : intro page to export tool where user can choose web service to export, * existent accounts and function mode (export/import). * * Copyright (C) 2017-2019 by Gilles Caulier * Copyright (C) 2018 by Thanh Trung Dinh * * 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, 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. * * ============================================================ */ #include "wsintropage.h" // Qt includes #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "dlayoutbox.h" #include "wswizard.h" #include "wssettings.h" namespace DigikamGenericUnifiedPlugin { class Q_DECL_HIDDEN WSIntroPage::Private { public: explicit Private(QWizard* const dialog) : imageGetOption(0), hbox(0), wizard(0), iface(0), wsOption(0), accountOption(0) { wizard = dynamic_cast(dialog); if (wizard) { iface = wizard->iface(); settings = wizard->settings(); } } QComboBox* imageGetOption; DHBox* hbox; WSWizard* wizard; WSSettings* settings; DInfoInterface* iface; QComboBox* wsOption; QComboBox* accountOption; }; WSIntroPage::WSIntroPage(QWizard* const dialog, const QString& title) : DWizardPage(dialog, title), d(new Private(dialog)) { DVBox* const vbox = new DVBox(this); QLabel* const desc = new QLabel(vbox); desc->setWordWrap(true); desc->setOpenExternalLinks(true); desc->setText(i18n("" "

Welcome to Web Services Tool

" "

This assistant will guide you step by step, to export " "your items to popular Internet data hosting service.

" "

Before exporting contents, you will be able to adjust items' properties " "according to your remote Web service capabilities.

" "
")); /* -------------------- * ComboBox for image selection method */ d->hbox = new DHBox(vbox); QLabel* const getImageLabel = new QLabel(i18n("&Choose operation:"), d->hbox); d->imageGetOption = new QComboBox(d->hbox); d->imageGetOption->insertItem(WSSettings::EXPORT, i18n("Export to web services")); d->imageGetOption->insertItem(WSSettings::IMPORT, i18n("Import from web services")); getImageLabel->setBuddy(d->imageGetOption); connect(d->imageGetOption, SIGNAL(currentIndexChanged(int)), this, SLOT(slotImageGetOptionChanged(int))); /* -------------------- * ComboBox for web service selection */ DHBox* const wsBox = new DHBox(vbox); QLabel* const wsLabel = new QLabel(i18n("&Choose remote Web Service:"), wsBox); d->wsOption = new QComboBox(wsBox); QMap map = WSSettings::webServiceNames(); QMap::const_iterator it = map.constBegin(); while (it != map.constEnd()) { QString wsName = it.value().toLower(); QIcon icon = QIcon::fromTheme(wsName.remove(QLatin1Char(' '))); d->wsOption->addItem(icon, it.value(), (int)it.key()); ++it; } wsLabel->setBuddy(d->wsOption); connect(d->wsOption, SIGNAL(currentIndexChanged(QString)), this, SLOT(slotWebServiceOptionChanged(QString))); /* -------------------- * ComboBox for user account selection * * An empty option is added to accounts, so that user can choose to login with new account */ DHBox* const accountBox = new DHBox(vbox); QLabel* const accountLabel = new QLabel(i18n("&Choose account:"), accountBox); d->accountOption = new QComboBox(accountBox); QStringList accounts = QStringList(QString()) << d->settings->allUserNames(map.constBegin().value()); - foreach(const QString& account, accounts) + foreach (const QString& account, accounts) { d->accountOption->addItem(account); } accountLabel->setBuddy(d->accountOption); /* -------------------- * Place widget in the view */ vbox->setStretchFactor(desc, 3); vbox->setStretchFactor(d->hbox, 1); vbox->setStretchFactor(wsBox, 3); vbox->setStretchFactor(accountBox, 3); setPageWidget(vbox); setLeftBottomPix(QIcon::fromTheme(QLatin1String("folder-html"))); } WSIntroPage::~WSIntroPage() { delete d; } void WSIntroPage::slotImageGetOptionChanged(int index) { QMap map = WSSettings::webServiceNames(); d->wsOption->clear(); /* * index == 0 <=> Export * index == 1 <=> Import, for now we only have Google Photo and Smugmug in this option */ if (index == 0) { QMap::const_iterator it = map.constBegin(); while (it != map.constEnd()) { QString wsName = it.value().toLower(); QIcon icon = QIcon::fromTheme(wsName.remove(QLatin1Char(' '))); qCDebug(DIGIKAM_WEBSERVICES_LOG) << wsName.remove(QLatin1Char(' ')); d->wsOption->addItem(icon, it.value(), (int)it.key()); ++it; } } else { d->wsOption->addItem(QIcon::fromTheme(QLatin1String("dk-smugmug")), map[WSSettings::WebService::SMUGMUG], WSSettings::WebService::SMUGMUG); d->wsOption->addItem(QIcon::fromTheme(QLatin1String("dk-googlephoto")), map[WSSettings::WebService::GPHOTO], WSSettings::WebService::GPHOTO); } } void WSIntroPage::slotWebServiceOptionChanged(const QString& serviceName) { d->accountOption->clear(); // An empty option is added to accounts, so that user can choose to login with new account QStringList accounts = QStringList(QString()) << d->settings->allUserNames(serviceName); - foreach(const QString& account, accounts) + foreach (const QString& account, accounts) { d->accountOption->addItem(account); } } void WSIntroPage::initializePage() { d->imageGetOption->setCurrentIndex(d->wizard->settings()->selMode); } bool WSIntroPage::validatePage() { d->wizard->settings()->selMode = (WSSettings::Selection)d->imageGetOption->currentIndex(); d->wizard->settings()->webService = (WSSettings::WebService)d->wsOption->currentIndex(); d->wizard->settings()->userName = d->accountOption->currentText(); return true; } } // namespace DigikamGenericUnifiedPlugin diff --git a/core/dplugins/generic/webservices/vkontakte/vkwindow.cpp b/core/dplugins/generic/webservices/vkontakte/vkwindow.cpp index 5d62408c64..45bf7a12e1 100644 --- a/core/dplugins/generic/webservices/vkontakte/vkwindow.cpp +++ b/core/dplugins/generic/webservices/vkontakte/vkwindow.cpp @@ -1,445 +1,445 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-11-15 * Description : a tool to export images to VKontakte web service * * Copyright (C) 2011-2015 by Alexander Potashev * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * Copyright (C) 2010 by Roman Tsisyk * * 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, 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. * * ============================================================ */ #include "vkwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // libkvkontakte includes #include #include // Local includes #include "digikam_version.h" #include "dprogresswdg.h" #include "vkalbumchooser.h" #include "vkauthwidget.h" namespace DigikamGenericVKontaktePlugin { class Q_DECL_HIDDEN VKWindow::Private { public: explicit Private() { import = false; mainWidget = nullptr; settingsBox = nullptr; headerLabel = nullptr; accountBox = nullptr; albumsBox = nullptr; imgList = nullptr; uploadWidget = nullptr; iface = nullptr; progressBar = nullptr; vkapi = nullptr; albumToSelect = -1; } bool import; /// User interface QWidget* mainWidget; QWidget* settingsBox; QLabel* headerLabel; /// accounts VKAuthWidget* accountBox; // album selection VKAlbumChooser* albumsBox; DItemsList* imgList; QWidget* uploadWidget; DInfoInterface* iface; DProgressWdg* progressBar; /// Pointers to running jobs QList jobs; Vkontakte::VkApi* vkapi; int albumToSelect; QString appId; }; VKWindow::VKWindow(DInfoInterface* const iface, QWidget* const parent, bool import) : WSToolDialog(parent, QLatin1String("VKontakte Dialog")), d(new Private) { d->iface = iface; d->vkapi = new Vkontakte::VkApi(this); // read settings from file readSettings(); connect(this, SIGNAL(finished(int)), this, SLOT(slotFinished())); d->import = import; d->mainWidget = new QWidget(this); QHBoxLayout* const mainLayout = new QHBoxLayout(d->mainWidget); d->imgList = new DItemsList(this); d->imgList->setObjectName(QLatin1String("WebService ImagesList")); d->imgList->setControlButtonsPlacement(DItemsList::ControlButtonsBelow); d->imgList->setAllowRAW(false); // TODO: implement conversion d->imgList->setIface(d->iface); d->imgList->loadImagesFromCurrentSelection(); d->imgList->listView()->setWhatsThis(i18n("This is the list of images " "to upload to your VKontakte album.")); d->settingsBox = new QWidget(this); QVBoxLayout* const settingsBoxLayout = new QVBoxLayout(d->settingsBox); d->headerLabel = new QLabel(d->settingsBox); d->headerLabel->setWhatsThis(i18n("This is a clickable link to open the " "VKontakte service in a web browser.")); d->headerLabel->setOpenExternalLinks(true); d->headerLabel->setFocusPolicy(Qt::NoFocus); // Account box initAccountBox(); // Album box d->albumsBox = new VKAlbumChooser(d->settingsBox, d->vkapi); d->albumsBox->selectAlbum(d->albumToSelect); // ------------------------------------------------------------------------ QGroupBox* const uploadBox = new QGroupBox(i18n("Destination"), d->settingsBox); uploadBox->setWhatsThis(i18n("This is the location where VKontakte images " "will be downloaded.")); QVBoxLayout* const uploadBoxLayout = new QVBoxLayout(uploadBox); d->uploadWidget = d->iface->uploadWidget(uploadBox); uploadBoxLayout->addWidget(d->uploadWidget); // ------------------------------------------------------------------------ #if 0 QGroupBox* const optionsBox = new QGroupBox(i18n("Options"), d->settingsBox); optionsBox->setWhatsThis(i18n("These are options that will be applied to images before upload.")); #endif // store state in rc file // d->checkKeepOriginal = new QCheckBox(i18n("Save in high resolution"), d->settingsBox); // QVBoxLayout* const optionsBoxLayout = new QVBoxLayout(optionsBox); // optionsBoxLayout->addWidget(d->checkKeepOriginal); d->progressBar = new DProgressWdg(d->settingsBox); d->progressBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); d->progressBar->hide(); // Layouts settingsBoxLayout->addWidget(d->headerLabel); settingsBoxLayout->addWidget(d->accountBox); settingsBoxLayout->addWidget(d->albumsBox); settingsBoxLayout->addWidget(uploadBox); // settingsBoxLayout->addWidget(optionsBox); settingsBoxLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); settingsBoxLayout->addWidget(d->progressBar); mainLayout->addWidget(d->imgList); mainLayout->addWidget(d->settingsBox); setMainWidget(d->mainWidget); setModal(false); if (!d->import) { setWindowTitle(i18nc("@title:window", "Export to VKontakte Web Service")); startButton()->setText(i18n("Start Upload")); startButton()->setToolTip(i18n("Start upload to VKontakte service")); setMinimumSize(700, 520); uploadBox->hide(); } else { // TODO: import support d->imgList->hide(); // optionsBox->hide(); } // UI slots connect(startButton(), SIGNAL(clicked(bool)), this, SLOT(slotStartTransfer())); // for startReactivation() connect(d->vkapi, SIGNAL(authenticated()), this, SLOT(show())); // Dialog update slots connect(this, SIGNAL(signalUpdateBusyStatus(bool)), this, SLOT(slotUpdateBusyStatus(bool))); // TBD: busy status handling needs improvement connect(d->vkapi, SIGNAL(authenticated()), this, SLOT(slotUpdateBusyStatusReady())); slotUpdateBusyStatus(true); startReactivation(); } VKWindow::~VKWindow() { reset(); delete d; } //--------------------------------------------------------------------------- void VKWindow::initAccountBox() { d->accountBox = new VKAuthWidget(d->settingsBox, d->vkapi); connect(d->vkapi, SIGNAL(authenticated()), this, SLOT(slotAuthenticated())); connect(d->accountBox, SIGNAL(signalAuthCleared()), this, SLOT(slotAuthCleared())); connect(d->accountBox, SIGNAL(signalUpdateAuthInfo()), this, SLOT(slotUpdateHeaderLabel())); } void VKWindow::startReactivation() { d->imgList->loadImagesFromCurrentSelection(); reset(); // show() will be called after that d->accountBox->slotStartAuthentication(false); } void VKWindow::reset() { emit signalUpdateBusyStatus(false); } void VKWindow::slotUpdateBusyStatus(bool busy) { if (d->albumsBox) d->albumsBox->setEnabled(!busy && d->vkapi->isAuthenticated()); if (!busy) { setCursor(Qt::ArrowCursor); startButton()->setEnabled(d->vkapi->isAuthenticated()); setRejectButtonMode(QDialogButtonBox::Close); } else { setCursor(Qt::WaitCursor); startButton()->setEnabled(false); setRejectButtonMode(QDialogButtonBox::Cancel); } } void VKWindow::slotUpdateBusyStatusReady() { slotUpdateBusyStatus(false); } //--------------------------------------------------------------------------- void VKWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("VKontakte Settings"); d->appId = grp.readEntry("VkAppId", "2446321"); d->albumToSelect = grp.readEntry("SelectedAlbumId", -1); d->vkapi->setAppId(d->appId); d->vkapi->setRequiredPermissions(Vkontakte::AppPermissions::Photos); d->vkapi->setInitialAccessToken(grp.readEntry("AccessToken", "")); } void VKWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("VKontakte Settings"); grp.writeEntry("VkAppId", d->appId); if (!d->vkapi->accessToken().isEmpty()) grp.writeEntry("AccessToken", d->vkapi->accessToken()); int aid = 0; if (!d->albumsBox->getCurrentAlbumId(aid)) { grp.deleteEntry("SelectedAlbumId"); } else { grp.writeEntry("SelectedAlbumId", aid); } } //--------------------------------------------------------------------------- void VKWindow::closeEvent(QCloseEvent* event) { if (!event) { return; } slotFinished(); event->accept(); } void VKWindow::slotFinished() { writeSettings(); reset(); } void VKWindow::slotAuthenticated() { if (d->albumsBox) d->albumsBox->setEnabled(true); } void VKWindow::slotAuthCleared() { if (d->albumsBox) { d->albumsBox->setEnabled(false); d->albumsBox->clearList(); } } void VKWindow::slotUpdateHeaderLabel() { d->headerLabel->setText(QString::fromLatin1("

" "%2

") .arg(d->accountBox->albumsURL()).arg(i18n("VKontakte"))); } //--------------------------------------------------------------------------- void VKWindow::handleVkError(KJob* kjob) { QMessageBox::critical(this, i18nc("@title:window", "Request to VKontakte failed"), kjob->errorText()); } //--------------------------------------------------------------------------- void VKWindow::slotStartTransfer() { int aid = 0; if (!d->albumsBox->getCurrentAlbumId(aid)) { // TODO: offer the user to create an album if there are no albums yet QMessageBox::information(this, QString(), i18n("Please select album first.")); return; } // TODO: import support if (!d->import) { emit signalUpdateBusyStatus(true); QStringList files; - foreach(const QUrl& url, d->imgList->imageUrls(true)) + foreach (const QUrl& url, d->imgList->imageUrls(true)) files.append(url.toLocalFile()); Vkontakte::UploadPhotosJob* const job = new Vkontakte::UploadPhotosJob(d->vkapi->accessToken(), files, false /*d->checkKeepOriginal->isChecked()*/, aid); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotPhotoUploadDone(KJob*))); connect(job, SIGNAL(progress(int)), d->progressBar, SLOT(setValue(int))); d->jobs.append(job); job->start(); } d->progressBar->show(); d->progressBar->progressScheduled(i18n("Vkontakte Export"), false, true); d->progressBar->progressThumbnailChanged( QIcon::fromTheme(QLatin1String("preferences-web-browser-shortcuts")).pixmap(22, 22)); } void VKWindow::slotPhotoUploadDone(KJob* kjob) { Vkontakte::UploadPhotosJob* const job = dynamic_cast(kjob); Q_ASSERT(job); d->jobs.removeAll(job); if (job == nullptr || job->error()) { handleVkError(job); } d->progressBar->hide(); d->progressBar->progressCompleted(); emit signalUpdateBusyStatus(false); } } // namespace DigikamGenericVKontaktePlugin diff --git a/core/dplugins/generic/webservices/yandexfotki/yfphoto.cpp b/core/dplugins/generic/webservices/yandexfotki/yfphoto.cpp index f1d389b760..6b7b565a96 100644 --- a/core/dplugins/generic/webservices/yandexfotki/yfphoto.cpp +++ b/core/dplugins/generic/webservices/yandexfotki/yfphoto.cpp @@ -1,139 +1,139 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-11-18 * Description : a tool to export items to YandexFotki web service * * Copyright (C) 2010 by Roman Tsisyk * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "yfphoto.h" namespace DigikamGenericYFPlugin { YFPhoto::YFPhoto() : m_access(ACCESS_PUBLIC), m_hideOriginal(false), m_disableComments(false), m_adult(false) { } YFPhoto::~YFPhoto() { } YFPhoto::YFPhoto(const QString& urn, const QString& author, const QString& title, const QString& summary, const QString& apiEditUrl, const QString& apiSelfUrl, const QString& apiMediaUrl, const QString& apiAlbumUrl, const QDateTime& publishedDate, const QDateTime& editedDate, const QDateTime& updatedDate, const QDateTime& createdDate, Access access, bool hideOriginal, bool disableComments, bool adult, const QString& remoteUrl) : m_urn(urn), m_author(author), m_title(title), m_summary(summary), m_apiEditUrl(apiEditUrl), m_apiSelfUrl(apiSelfUrl), m_apiMediaUrl(apiMediaUrl), m_apiAlbumUrl(apiAlbumUrl), m_publishedDate(publishedDate), m_editedDate(editedDate), m_updatedDate(updatedDate), m_createdDate(createdDate), m_access(access), m_hideOriginal(hideOriginal), m_disableComments(disableComments), m_adult(adult), m_remoteUrl(remoteUrl) { // nothing } YFPhoto::YFPhoto(const YFPhoto& other) : m_urn(other.urn()), m_author(other.author()), m_title(other.title()), m_summary(other.summary()), m_apiEditUrl(other.m_apiEditUrl), m_apiSelfUrl(other.m_apiSelfUrl), m_apiMediaUrl(other.m_apiMediaUrl), m_apiAlbumUrl(other.m_apiAlbumUrl), m_publishedDate(other.publishedDate()), m_editedDate(other.editedDate()), m_updatedDate(other.updatedDate()), m_createdDate(other.createdDate()), m_access(other.access()), m_hideOriginal(other.isHideOriginal()), m_disableComments(other.isDisableComments()), m_adult(other.isAdult()), m_remoteUrl(other.remoteUrl()), m_localUrl(other.localUrl()), m_originalUrl(other.originalUrl()) { //nothing } QDebug operator<<(QDebug d, const YFPhoto& p) { d.nospace() << "YFPhoto(\n"; d.space() << "urn:" << p.urn() << ",\n"; d.space() << "author:" << p.author() << ",\n"; d.space() << "title:" << p.title() << ",\n"; d.space() << "summary:" << p.summary() << ",\n"; d.space() << "apiEditUrl:" << p.m_apiEditUrl << ",\n"; d.space() << "apiSelfUrl:" << p.m_apiSelfUrl << ",\n"; d.space() << "apiMediaUrl:" << p.m_apiMediaUrl << ",\n"; d.space() << "apiAlbumUrl:" << p.m_apiAlbumUrl << ",\n"; d.space() << "publishedDate:" << p.publishedDate() << ",\n"; d.space() << "editedDate:" << p.editedDate() << ",\n"; d.space() << "updatedDate:" << p.updatedDate() << ",\n"; d.space() << "createdDate:" << p.createdDate() << ",\n"; d.space() << "access:" << p.access() << ",\n"; d.space() << "hideOriginal:" << p.isHideOriginal() << ",\n"; d.space() << "disableComments:" << p.isDisableComments() << ",\n"; d.space() << "adult:" << p.isAdult() << ",\n"; d.space() << "remoteUrl:" << p.remoteUrl() << ",\n"; d.space() << "localUrl:" << p.localUrl() << ",\n"; d.space() << "originalUrl:" << p.originalUrl() << ",\n"; d.space() << "tags:" << "\n"; - foreach(const QString& t, p.tags) + foreach (const QString& t, p.tags) { d.space() << t << ","; } d.space() << "\n"; d.nospace() << ")"; return d; } } // namespace DigikamGenericYFPlugin diff --git a/core/dplugins/generic/webservices/yandexfotki/yftalker.cpp b/core/dplugins/generic/webservices/yandexfotki/yftalker.cpp index c6a49af652..e065e7f4ec 100644 --- a/core/dplugins/generic/webservices/yandexfotki/yftalker.cpp +++ b/core/dplugins/generic/webservices/yandexfotki/yftalker.cpp @@ -1,1178 +1,1178 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-11-14 * Description : a tool to export items to YandexFotki web service * * Copyright (C) 2010 by Roman Tsisyk * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "yftalker.h" // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_version.h" #include "yfauth.h" #include "yfalbum.h" namespace DigikamGenericYFPlugin { class Q_DECL_HIDDEN YFTalker::Private { public: explicit Private() { state = STATE_UNAUTHENTICATED; lastPhoto = nullptr; netMngr = nullptr; reply = nullptr; } // API-related fields QString sessionKey; QString sessionId; QString token; QString login; QString password; QString apiAlbumsUrl; QString apiPhotosUrl; QString apiTagsUrl; // FSM data State state; // temporary data YFPhoto* lastPhoto; QString lastPhotosUrl; // for albums pagination //in listAlbums() QList albums; QString albumsNextUrl; QList photos; QString photosNextUrl; QNetworkAccessManager* netMngr; QNetworkReply* reply; // Data buffer QByteArray buffer; // constants // use QString instead of QUrl, we need .arg static const QString SESSION_URL; static const QString TOKEN_URL; static const QString SERVICE_URL; static const QString AUTH_REALM; static const QString ACCESS_STRINGS[]; }; /* * static API constants */ const QString YFTalker::Private::SESSION_URL = QLatin1String("http://auth.mobile.yandex.ru/yamrsa/key/"); const QString YFTalker::Private::AUTH_REALM = QLatin1String("fotki.yandex.ru"); const QString YFTalker::Private::TOKEN_URL = QLatin1String("http://auth.mobile.yandex.ru/yamrsa/token/"); const QString YFTalker::Private::SERVICE_URL = QLatin1String("http://api-fotki.yandex.ru/api/users/%1/"); const QString YFTalker::Private::ACCESS_STRINGS[] = { QLatin1String("public"), QLatin1String("friends"), QLatin1String("private") }; const QString YFTalker::USERPAGE_URL = QLatin1String("http://fotki.yandex.ru/users/%1/"); const QString YFTalker::USERPAGE_DEFAULT_URL = QLatin1String("http://fotki.yandex.ru/"); // ------------------------------------------------------------ YFTalker::YFTalker(QObject* const parent) : QObject(parent), d(new Private) { d->netMngr = new QNetworkAccessManager(this); connect(d->netMngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); } YFTalker::~YFTalker() { reset(); delete d; } YFTalker::State YFTalker::state() const { return d->state; } const QString& YFTalker::sessionKey() const { return d->sessionKey; } const QString& YFTalker::sessionId() const { return d->sessionId; } const QString& YFTalker::token() const { return d->token; } const QString& YFTalker::login() const { return d->login; } void YFTalker::setLogin(const QString& login) { d->login = login; } const QString& YFTalker::password() const { return d->password; } void YFTalker::setPassword(const QString& password) { d->password = password; } bool YFTalker::isAuthenticated() const { return (d->state & STATE_AUTHENTICATED) != 0; } bool YFTalker::isErrorState() const { return (d->state & STATE_ERROR) != 0; } const QList& YFTalker::albums() const { return d->albums; } const QList& YFTalker::photos() const { return d->photos; } void YFTalker::getService() { d->state = STATE_GETSERVICE; QUrl url(d->SERVICE_URL.arg(d->login)); d->reply = d->netMngr->get(QNetworkRequest(url)); d->buffer.resize(0); } /* void YFTalker::checkToken() { // try to get something with our token, if it is invalid catch 401 d->state = STATE_CHECKTOKEN; QUrl url(d->apiAlbumsUrl); QNetworkRequest netRequest(url); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(AUTH_REALM).arg(d->token).toLatin1()); d->reply = d->netMngr->get(netRequest); // Error: STATE_CHECKTOKEN_INVALID // Function: slotParseResponseCheckToken() d->buffer.resize(0); } */ void YFTalker::getSession() { if (d->state != STATE_GETSERVICE_DONE) return; d->state = STATE_GETSESSION; QUrl url(d->SESSION_URL); d->reply = d->netMngr->get(QNetworkRequest(url)); d->buffer.resize(0); } void YFTalker::getToken() { if (d->state != STATE_GETSESSION_DONE) return; const QString credentials = YFAuth::makeCredentials(d->sessionKey, d->login, d->password); // prepare params QStringList paramList; paramList.append(QLatin1String("request_id=") + d->sessionId); paramList.append(QLatin1String("credentials=") + QString::fromUtf8(QUrl::toPercentEncoding(credentials))); QString params = paramList.join(QLatin1Char('&')); d->state = STATE_GETTOKEN; QUrl url(d->TOKEN_URL); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); d->reply = d->netMngr->post(netRequest, params.toUtf8()); d->buffer.resize(0); } void YFTalker::listAlbums() { if (isErrorState() || !isAuthenticated()) return; d->albumsNextUrl = d->apiAlbumsUrl; d->albums.clear(); listAlbumsNext(); } void YFTalker::listAlbumsNext() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listAlbumsNext"; d->state = STATE_LISTALBUMS; QUrl url(d->albumsNextUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=feed")); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(d->AUTH_REALM).arg(d->token).toLatin1()); d->reply = d->netMngr->get(netRequest); d->buffer.resize(0); } void YFTalker::listPhotos(const YandexFotkiAlbum& album) { if (isErrorState() || !isAuthenticated()) return; d->photosNextUrl = album.m_apiPhotosUrl; d->photos.clear(); listPhotosNext(); } // protected member void YFTalker::listPhotosNext() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listPhotosNext"; d->state = STATE_LISTPHOTOS; QUrl url(d->photosNextUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=feed")); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(d->AUTH_REALM).arg(d->token).toLatin1()); d->reply = d->netMngr->get(netRequest); d->buffer.resize(0); } void YFTalker::updatePhoto(YFPhoto& photo, const YandexFotkiAlbum& album) { if (isErrorState() || !isAuthenticated()) return; // sanity check if (photo.title().isEmpty()) { photo.setTitle(QFileInfo(photo.localUrl()).baseName().trimmed()); } // move photo to another album (if changed) photo.m_apiAlbumUrl = album.m_apiSelfUrl; // FIXME: hack d->lastPhotosUrl = album.m_apiPhotosUrl; if (!photo.remoteUrl().isNull()) { // TODO: updating image file haven't yet supported by API // so, just update info return updatePhotoInfo(photo); } else { // for new images also upload file updatePhotoFile(photo); } } void YFTalker::updatePhotoFile(YFPhoto& photo) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "updatePhotoFile" << photo; QFile imageFile(photo.localUrl()); if (!imageFile.open(QIODevice::ReadOnly)) { setErrorState(STATE_UPDATEPHOTO_FILE_ERROR); return; } d->state = STATE_UPDATEPHOTO_FILE; d->lastPhoto = &photo; QUrl url(d->lastPhotosUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("image/jpeg")); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(d->AUTH_REALM).arg(d->token).toLatin1()); netRequest.setRawHeader("Slug", QUrl::toPercentEncoding(photo.title()) + ".jpg"); d->reply = d->netMngr->post(netRequest, imageFile.readAll()); d->buffer.resize(0); imageFile.close(); } void YFTalker::updatePhotoInfo(YFPhoto& photo) { QDomDocument doc; QDomProcessingInstruction instr = doc.createProcessingInstruction( QLatin1String("xml"), QLatin1String("version='1.0' encoding='UTF-8'")); doc.appendChild(instr); QDomElement entryElem = doc.createElement(QLatin1String("entry")); entryElem.setAttribute(QLatin1String("xmlns"), QLatin1String("http://www.w3.org/2005/Atom")); entryElem.setAttribute(QLatin1String("xmlns:f"), QLatin1String("yandex:fotki")); doc.appendChild(entryElem); QDomElement urn = doc.createElement(QLatin1String("urn")); urn.appendChild(doc.createTextNode(photo.urn())); entryElem.appendChild(urn); QDomElement title = doc.createElement(QLatin1String("title")); title.appendChild(doc.createTextNode(photo.title())); entryElem.appendChild(title); QDomElement linkAlbum = doc.createElement(QLatin1String("link")); linkAlbum.setAttribute(QLatin1String("href"), photo.m_apiAlbumUrl); linkAlbum.setAttribute(QLatin1String("rel"), QLatin1String("album")); entryElem.appendChild(linkAlbum); QDomElement summary = doc.createElement(QLatin1String("summary")); summary.appendChild(doc.createTextNode(photo.summary())); entryElem.appendChild(summary); QDomElement adult = doc.createElement(QLatin1String("f:xxx")); adult.setAttribute(QLatin1String("value"), photo.isAdult() ? QLatin1String("true") : QLatin1String("false")); entryElem.appendChild(adult); QDomElement hideOriginal = doc.createElement(QLatin1String("f:hide_original")); hideOriginal.setAttribute(QLatin1String("value"), photo.isHideOriginal() ? QLatin1String("true") : QLatin1String("false")); entryElem.appendChild(hideOriginal); QDomElement disableComments = doc.createElement(QLatin1String("f:disable_comments")); disableComments.setAttribute(QLatin1String("value"), photo.isDisableComments() ? QLatin1String("true") : QLatin1String("false")); entryElem.appendChild(disableComments); QDomElement access = doc.createElement(QLatin1String("f:access")); access.setAttribute(QLatin1String("value"), d->ACCESS_STRINGS[photo.access()]); entryElem.appendChild(access); // FIXME: undocumented API - foreach(const QString& t, photo.tags) + foreach (const QString& t, photo.tags) { QDomElement tag = doc.createElement(QLatin1String("category")); tag.setAttribute(QLatin1String("scheme"), d->apiTagsUrl); tag.setAttribute(QLatin1String("term"), t); entryElem.appendChild(tag); } QByteArray buffer = doc.toString(1).toUtf8(); // with idents qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Prepared data: " << buffer; d->lastPhoto = &photo; d->state = STATE_UPDATEPHOTO_INFO; QUrl url(photo.m_apiEditUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=entry")); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(d->AUTH_REALM).arg(d->token).toLatin1()); d->reply = d->netMngr->put(netRequest, buffer); d->buffer.resize(0); } void YFTalker::updateAlbum(YandexFotkiAlbum& album) { if (isErrorState() || !isAuthenticated()) return; if (album.urn().isEmpty()) { // new album return updateAlbumCreate(album); } else { qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Updating albums is not yet supported"; } } void YFTalker::updateAlbumCreate(YandexFotkiAlbum& album) { QDomDocument doc; QDomProcessingInstruction instr = doc.createProcessingInstruction( QLatin1String("xml"), QLatin1String("version='1.0' encoding='UTF-8'")); doc.appendChild(instr); QDomElement entryElem = doc.createElement(QLatin1String("entry")); entryElem.setAttribute(QLatin1String("xmlns"), QLatin1String("http://www.w3.org/2005/Atom")); entryElem.setAttribute(QLatin1String("xmlns:f"), QLatin1String("yandex:fotki")); doc.appendChild(entryElem); QDomElement title = doc.createElement(QLatin1String("title")); title.appendChild(doc.createTextNode(album.title())); entryElem.appendChild(title); QDomElement summary = doc.createElement(QLatin1String("summary")); summary.appendChild(doc.createTextNode(album.summary())); entryElem.appendChild(summary); QDomElement password = doc.createElement(QLatin1String("f:password")); password.appendChild(doc.createTextNode(album.m_password)); entryElem.appendChild(password); const QByteArray postData = doc.toString(1).toUtf8(); // with idents qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Prepared data: " << postData; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Url" << d->apiAlbumsUrl; d->state = STATE_UPDATEALBUM; QUrl url(d->apiAlbumsUrl); QNetworkRequest netRequest(url); netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=entry")); netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"") .arg(d->AUTH_REALM).arg(d->token).toLatin1()); d->reply = d->netMngr->post(netRequest, postData); d->buffer.resize(0); } void YFTalker::reset() { if (d->reply) { d->reply->abort(); d->reply = nullptr; } d->token.clear(); d->state = STATE_UNAUTHENTICATED; } void YFTalker::cancel() { if (d->reply) { d->reply->abort(); d->reply = nullptr; } if (isAuthenticated()) { d->state = STATE_AUTHENTICATED; } else { d->token.clear(); d->state = STATE_UNAUTHENTICATED; } } void YFTalker::setErrorState(State state) { d->state = state; emit signalError(); } void YFTalker::slotFinished(QNetworkReply* reply) { if (reply != d->reply) { return; } d->reply = nullptr; if (reply->error() != QNetworkReply::NoError) { int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Transfer Error" << code << reply->errorString(); if (code == 401 || code == 403 || code == 404) // auth required, 404 user not found { setErrorState(STATE_INVALID_CREDENTIALS); } else if (d->state == STATE_GETSERVICE) { setErrorState(STATE_GETSERVICE_ERROR); } else if (d->state == STATE_GETSESSION) { setErrorState(STATE_GETSESSION_ERROR); } else if (d->state == STATE_GETTOKEN) { setErrorState(STATE_GETTOKEN_ERROR); } else if (d->state == STATE_LISTALBUMS) { setErrorState(STATE_LISTALBUMS_ERROR); } else if (d->state == STATE_LISTPHOTOS) { setErrorState(STATE_LISTPHOTOS_ERROR); } else if (d->state == STATE_UPDATEPHOTO_FILE) { setErrorState(STATE_UPDATEPHOTO_FILE_ERROR); } else if (d->state == STATE_UPDATEPHOTO_INFO) { setErrorState(STATE_UPDATEPHOTO_INFO_ERROR); } else if (d->state == STATE_UPDATEALBUM) { setErrorState(STATE_UPDATEALBUM_ERROR); } reply->deleteLater(); return; } d->buffer.append(reply->readAll()); switch(d->state) { case (STATE_GETSERVICE): slotParseResponseGetService(); break; case (STATE_GETSESSION): slotParseResponseGetSession(); break; case (STATE_GETTOKEN): slotParseResponseGetToken(); break; case (STATE_LISTALBUMS): slotParseResponseListAlbums(); break; case (STATE_LISTPHOTOS): slotParseResponseListPhotos(); break; case (STATE_UPDATEPHOTO_FILE): slotParseResponseUpdatePhotoFile(); break; case (STATE_UPDATEPHOTO_INFO): slotParseResponseUpdatePhotoInfo(); break; case (STATE_UPDATEALBUM): slotParseResponseUpdateAlbum(); break; default: break; } reply->deleteLater(); } void YFTalker::slotParseResponseGetService() { QDomDocument doc(QLatin1String("service")); if (!doc.setContent(d->buffer)) { qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer; return setErrorState(STATE_GETSERVICE_ERROR); } const QDomElement rootElem = doc.documentElement(); QDomElement workspaceElem = rootElem.firstChildElement(QLatin1String("app:workspace")); // FIXME: workaround for Yandex xml namespaces bugs QString prefix = QLatin1String("app:"); if (workspaceElem.isNull()) { workspaceElem = rootElem.firstChildElement(QLatin1String("workspace")); prefix = QString(); qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Service document without namespaces found"; } if (workspaceElem.isNull()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: workspace element"; return setErrorState(STATE_GETSERVICE_ERROR); } QString apiAlbumsUrl; QString apiPhotosUrl; QString apiTagsUrl; QDomElement collectionElem = workspaceElem.firstChildElement(prefix + QLatin1String("collection")); for ( ; !collectionElem.isNull() ; collectionElem = collectionElem.nextSiblingElement(prefix + QLatin1String("collection"))) { const QDomElement acceptElem = collectionElem.firstChildElement(prefix + QLatin1String("accept")); if (acceptElem.isNull()) // invalid section, ignore { continue; } // FIXME: id attribute is undocumented if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("album-list")) { apiAlbumsUrl = collectionElem.attribute(QLatin1String("href")); } else if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("photo-list")) { apiPhotosUrl = collectionElem.attribute(QLatin1String("href")); } else if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("tag-list")) { apiTagsUrl = collectionElem.attribute(QLatin1String("href")); } // else skip unknown section } if (apiAlbumsUrl.isNull() || apiPhotosUrl.isNull()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: service URLs"; return setErrorState(STATE_GETSERVICE_ERROR); } d->apiAlbumsUrl = apiAlbumsUrl; d->apiPhotosUrl = apiPhotosUrl; d->apiTagsUrl = apiTagsUrl; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ServiceUrls:"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Albums" << d->apiAlbumsUrl; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photos" << d->apiPhotosUrl; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags" << d->apiTagsUrl; d->state = STATE_GETSERVICE_DONE; emit signalGetServiceDone(); } /* void YFTalker::slotParseResponseCheckToken() { // token still valid, skip getSession and getToken d->state = STATE_GETTOKEN_DONE; emit signalGetTokenDone(); } */ void YFTalker::slotParseResponseGetSession() { QDomDocument doc(QLatin1String("session")); if (!doc.setContent(d->buffer)) { return setErrorState(STATE_GETSESSION_ERROR); } const QDomElement rootElem = doc.documentElement(); const QDomElement keyElem = rootElem.firstChildElement(QLatin1String("key")); const QDomElement requestIdElem = rootElem.firstChildElement(QLatin1String("request_id")); if (keyElem.isNull() || keyElem.nodeType() != QDomNode::ElementNode || requestIdElem.isNull() || requestIdElem.nodeType() != QDomNode::ElementNode) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML" << d->buffer; return setErrorState(STATE_GETSESSION_ERROR); } d->sessionKey = keyElem.text(); d->sessionId = requestIdElem.text(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Session started" << d->sessionKey << d->sessionId; d->state = STATE_GETSESSION_DONE; emit signalGetSessionDone(); } void YFTalker::slotParseResponseGetToken() { QDomDocument doc(QLatin1String("response")); if (!doc.setContent(d->buffer)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer; return setErrorState(STATE_GETTOKEN_ERROR); } const QDomElement rootElem = doc.documentElement(); const QDomElement tokenElem = rootElem.firstChildElement(QLatin1String("token")); if (tokenElem.isNull() || tokenElem.nodeType() != QDomNode::ElementNode) { const QDomElement errorElem = rootElem.firstChildElement(QLatin1String("error")); if (errorElem.isNull() || errorElem.nodeType() != QDomNode::ElementNode) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Auth unknown error"; return setErrorState(STATE_GETTOKEN_ERROR); } /* // checked by HTTP error code in prepareJobResult const QString errorCode = errorElem.attribute("code", "0"); qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString("Auth error: %1, code=%2").arg(errorElem.text()).arg(errorCode); if (errorCode == "2") { // Invalid credentials return setErrorState(STATE_GETTOKEN_INVALID_CREDENTIALS); } */ return; } d->token = tokenElem.text(); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Token got" << d->token; d->state = STATE_GETTOKEN_DONE; emit signalGetTokenDone(); } void YFTalker::slotParseResponseListAlbums() { QDomDocument doc(QLatin1String("feed")); if (!doc.setContent(d->buffer)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error"; return setErrorState(STATE_LISTALBUMS_ERROR); } bool errorOccurred = false; const QDomElement rootElem = doc.documentElement(); // find next page link d->albumsNextUrl.clear(); QDomElement linkElem = rootElem.firstChildElement(QLatin1String("link")); for ( ; !linkElem.isNull() ; linkElem = linkElem.nextSiblingElement(QLatin1String("link"))) { if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("next") && !linkElem.attribute(QLatin1String("href")).isNull()) { d->albumsNextUrl = linkElem.attribute(QLatin1String("href")); break; } } QDomElement entryElem = rootElem.firstChildElement(QLatin1String("entry")); for ( ; !entryElem.isNull() ; entryElem = entryElem.nextSiblingElement(QLatin1String("entry"))) { const QDomElement urn = entryElem.firstChildElement(QLatin1String("id")); const QDomElement author = entryElem.firstChildElement(QLatin1String("author")); const QDomElement title = entryElem.firstChildElement(QLatin1String("title")); const QDomElement summary = entryElem.firstChildElement(QLatin1String("summary")); const QDomElement published = entryElem.firstChildElement(QLatin1String("published")); const QDomElement edited = entryElem.firstChildElement(QLatin1String("app:edited")); const QDomElement updated = entryElem.firstChildElement(QLatin1String("updated")); const QDomElement prot = entryElem.firstChildElement(QLatin1String("protected")); QDomElement linkSelf; QDomElement linkEdit; QDomElement linkPhotos; QDomElement linkElem = entryElem.firstChildElement(QLatin1String("link")); for ( ; !linkElem.isNull() ; linkElem = linkElem.nextSiblingElement(QLatin1String("link"))) { if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("self")) linkSelf = linkElem; else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit")) linkEdit = linkElem; else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("photos")) linkPhotos = linkElem; // else skip } if (urn.isNull() || title.isNull() || linkSelf.isNull() || linkEdit.isNull() || linkPhotos.isNull()) { errorOccurred = true; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: invalid entry on line" << entryElem.lineNumber(); // simple skip this record, no additional messages to user continue; } QString password; if (!prot.isNull() && prot.attribute(QLatin1String("value"), QLatin1String("false")) == QLatin1String("true")) { password = QLatin1String(""); // set not null value } d->albums.append(YandexFotkiAlbum( urn.text(), author.text(), title.text(), summary.text(), linkEdit.attribute(QLatin1String("href")), linkSelf.attribute(QLatin1String("href")), linkPhotos.attribute(QLatin1String("href")), QDateTime::fromString(published.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")), QDateTime::fromString(edited.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")), QDateTime::fromString(updated.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")), password )); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Found album:" << d->albums.last(); } // TODO: pagination like listPhotos // if an error has occurred and we didn't find anything => notify user if (errorOccurred && d->albums.isEmpty()) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "No result and errors have occurred"; return setErrorState(STATE_LISTALBUMS_ERROR); } // we have next page if (!d->albumsNextUrl.isNull()) { return listAlbumsNext(); } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List albums done: " << d->albums.size(); d->state = STATE_LISTALBUMS_DONE; emit signalListAlbumsDone(d->albums); } } bool YFTalker::slotParsePhotoXml(const QDomElement& entryElem, YFPhoto& photo) { const QDomElement urn = entryElem.firstChildElement(QLatin1String("id")); const QDomElement author = entryElem.firstChildElement(QLatin1String("author")); const QDomElement title = entryElem.firstChildElement(QLatin1String("title")); const QDomElement summary = entryElem.firstChildElement(QLatin1String("summary")); const QDomElement published = entryElem.firstChildElement(QLatin1String("published")); const QDomElement edited = entryElem.firstChildElement(QLatin1String("app:edited")); const QDomElement updated = entryElem.firstChildElement(QLatin1String("updated")); const QDomElement created = entryElem.firstChildElement(QLatin1String("f:created")); const QDomElement accessAttr = entryElem.firstChildElement(QLatin1String("f:access")); const QDomElement hideOriginal = entryElem.firstChildElement(QLatin1String("f:hide_original")); const QDomElement disableComments = entryElem.firstChildElement(QLatin1String("f:disable_comments")); const QDomElement adult = entryElem.firstChildElement(QLatin1String("f:xxx")); const QDomElement content = entryElem.firstChildElement(QLatin1String("content")); QDomElement linkSelf; QDomElement linkEdit; QDomElement linkMedia; QDomElement linkAlbum; QDomElement linkElem = entryElem.firstChildElement(QLatin1String("link")); for ( ; !linkElem.isNull() ; linkElem = linkElem.nextSiblingElement(QLatin1String("link"))) { if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("self")) linkSelf = linkElem; else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit")) linkEdit = linkElem; else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit-media")) linkMedia = linkElem; else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("album")) linkAlbum = linkElem; // else skip } // XML sanity checks if (urn.isNull() || title.isNull() || linkSelf.isNull() || linkEdit.isNull() || linkMedia.isNull() || linkAlbum.isNull() || !content.hasAttribute(QLatin1String("src")) || !accessAttr.hasAttribute(QLatin1String("value"))) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data, error on line" << entryElem.lineNumber(); // simple skip this record, no additional messages to user return false; } const QString accessString = accessAttr.attribute(QLatin1String("value")); YFPhoto::Access access; if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_PRIVATE]) access = YFPhoto::ACCESS_PRIVATE; else if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_FRIENDS]) access = YFPhoto::ACCESS_FRIENDS; else if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_PUBLIC]) access = YFPhoto::ACCESS_PUBLIC; else { qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Unknown photo access level: " << accessString; access = YFPhoto::ACCESS_PUBLIC; } photo.m_urn = urn.text(); photo.m_author = author.text(); photo.setTitle(title.text()); photo.setSummary(summary.text()); photo.m_apiEditUrl = linkEdit.attribute(QLatin1String("href")); photo.m_apiSelfUrl = linkSelf.attribute(QLatin1String("href")); photo.m_apiMediaUrl = linkMedia.attribute(QLatin1String("href")); photo.m_apiAlbumUrl = linkAlbum.attribute(QLatin1String("href")); photo.m_publishedDate = QDateTime::fromString(published.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")); photo.m_editedDate = QDateTime::fromString(edited.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")); photo.m_updatedDate = QDateTime::fromString(updated.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")); photo.m_createdDate = QDateTime::fromString(created.text(), QLatin1String("yyyy-MM-ddTHH:mm:ss")); photo.setAccess(access); photo.setHideOriginal(hideOriginal.attribute( QLatin1String("value"), QLatin1String("false")) == QLatin1String("true")); photo.setDisableComments(disableComments.attribute( QLatin1String("value"), QLatin1String("false")) == QLatin1String("true")); photo.setAdult(adult.attribute( QLatin1String("value"), QLatin1String("false")) == QLatin1String("true")); photo.m_remoteUrl = content.attribute(QLatin1String("src")); /* * FIXME: tags part of the API is not documented by Yandex */ // reload all tags from the response photo.tags.clear(); QDomElement category = entryElem.firstChildElement(QLatin1String("category")); for ( ; !category.isNull() ; category = category.nextSiblingElement(QLatin1String("category"))) { if (category.hasAttribute(QLatin1String("term")) && category.hasAttribute(QLatin1String("scheme")) && // FIXME: I have no idea how to make its better, usable API is needed category.attribute(QLatin1String("scheme")) == d->apiTagsUrl) { photo.tags.append(category.attribute(QLatin1String("term"))); } } return true; } void YFTalker::slotParseResponseListPhotos() { QDomDocument doc(QLatin1String("feed")); if (!doc.setContent(d->buffer)) { qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, parse error: " << d->buffer; return setErrorState(STATE_LISTPHOTOS_ERROR); } int initialSize = d->photos.size(); bool errorOccurred = false; const QDomElement rootElem = doc.documentElement(); // find next page link d->photosNextUrl.clear(); QDomElement linkElem = rootElem.firstChildElement(QLatin1String("link")); for ( ; !linkElem.isNull() ; linkElem = linkElem.nextSiblingElement(QLatin1String("link"))) { if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("next") && !linkElem.attribute(QLatin1String("href")).isNull()) { d->photosNextUrl = linkElem.attribute(QLatin1String("href")); break; } } QDomElement entryElem = rootElem.firstChildElement(QLatin1String("entry")); for ( ; !entryElem.isNull() ; entryElem = entryElem.nextSiblingElement(QLatin1String("entry"))) { YFPhoto photo; if (slotParsePhotoXml(entryElem, photo)) { d->photos.append(photo); } else { // set error mark and continue errorOccurred = true; } } // if an error has occurred and we didn't find anything => notify user if (errorOccurred && initialSize == d->photos.size()) { qCCritical(DIGIKAM_WEBSERVICES_LOG) << "No photos found, some XML errors have occurred"; return setErrorState(STATE_LISTPHOTOS_ERROR); } // we have next page if (!d->photosNextUrl.isNull()) { return listPhotosNext(); } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List photos done: " << d->photos.size(); d->state = STATE_LISTPHOTOS_DONE; emit signalListPhotosDone(d->photos); } } void YFTalker::slotParseResponseUpdatePhotoFile() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Uploaded photo document" << d->buffer; QDomDocument doc(QLatin1String("entry")); if (!doc.setContent(d->buffer)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, parse error" << d->buffer; return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR); } YFPhoto& photo = *d->lastPhoto; YFPhoto tmpPhoto; const QDomElement entryElem = doc.documentElement(); if (!slotParsePhotoXml(entryElem, tmpPhoto)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, entry not found" << d->buffer; return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR); } photo.m_urn = tmpPhoto.m_urn; photo.m_apiEditUrl = tmpPhoto.m_apiEditUrl; photo.m_apiSelfUrl = tmpPhoto.m_apiSelfUrl; photo.m_apiMediaUrl = tmpPhoto.m_apiMediaUrl; photo.m_remoteUrl = tmpPhoto.m_remoteUrl; photo.m_author = tmpPhoto.m_author; // update info updatePhotoInfo(photo); } void YFTalker::slotParseResponseUpdatePhotoInfo() { YFPhoto& photo = *d->lastPhoto; /* // reload all information QDomDocument doc("entry"); if (!doc.setContent(d->buffer)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer; return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR); } const QDomElement entryElem = doc.documentElement(); if (!slotParsePhotoXml(entryElem, photo)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Can't reload photo after uploading"; return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR); } */ d->state = STATE_UPDATEPHOTO_DONE; d->lastPhoto = nullptr; emit signalUpdatePhotoDone(photo); } void YFTalker::slotParseResponseUpdateAlbum() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Updated album" << d->buffer; d->state = STATE_UPDATEALBUM_DONE; d->lastPhoto = nullptr; emit signalUpdateAlbumDone(); } } // namespace DigikamGenericYFPlugin diff --git a/core/dplugins/generic/webservices/yandexfotki/yfwindow.cpp b/core/dplugins/generic/webservices/yandexfotki/yfwindow.cpp index 9c9d4ba1e7..69a6c21cf5 100644 --- a/core/dplugins/generic/webservices/yandexfotki/yfwindow.cpp +++ b/core/dplugins/generic/webservices/yandexfotki/yfwindow.cpp @@ -1,841 +1,841 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-11-15 * Description : a tool to export items to YandexFotki web service * * Copyright (C) 2010 by Roman Tsisyk * Copyright (C) 2005-2008 by Vardhman Jain * Copyright (C) 2008-2019 by Gilles Caulier * Copyright (C) 2009 by Luka Renko * * 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, 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. * * ============================================================ */ #include "yfwindow.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "digikam_version.h" #include "ditemslist.h" #include "yftalker.h" #include "yfnewalbumdlg.h" #include "digikam_debug.h" #include "wstoolutils.h" #include "wslogindialog.h" #include "yfwidget.h" #include "previewloadthread.h" #include "dprogresswdg.h" #include "dmetadata.h" namespace DigikamGenericYFPlugin { class Q_DECL_HIDDEN YFWindow::Private { public: explicit Private() { import = false; widget = nullptr; loginLabel = nullptr; headerLabel = nullptr; changeUserButton = nullptr; albumsBox = nullptr; newAlbumButton = nullptr; reloadAlbumsButton = nullptr; albumsCombo = nullptr; accessCombo = nullptr; hideOriginalCheck = nullptr; disableCommentsCheck = nullptr; adultCheck = nullptr; resizeCheck = nullptr; dimensionSpin = nullptr; imageQualitySpin = nullptr; policyGroup = nullptr; imgList = nullptr; progressBar = nullptr; iface = nullptr; } bool import; YFWidget* widget; // User interface QLabel* loginLabel; QLabel* headerLabel; QPushButton* changeUserButton; // albums QGroupBox* albumsBox; QPushButton* newAlbumButton; QPushButton* reloadAlbumsButton; QComboBox* albumsCombo; // upload settings QComboBox* accessCombo; QCheckBox* hideOriginalCheck; QCheckBox* disableCommentsCheck; QCheckBox* adultCheck; QCheckBox* resizeCheck; QSpinBox* dimensionSpin; QSpinBox* imageQualitySpin; QButtonGroup* policyGroup; DItemsList* imgList; DProgressWdg* progressBar; DInfoInterface* iface; // Backend QString tmpDir; YFTalker talker; QStack transferQueue; DMetadata meta; // XMP id const for images static const char* XMP_SERVICE_ID; }; /* * This tag added to our images after uploading to Fotki web service */ const char* YFWindow::Private::XMP_SERVICE_ID = "Xmp.digiKam.yandexGPhotoId"; YFWindow::YFWindow(DInfoInterface* const iface, QWidget* const /*parent*/, bool import) : WSToolDialog(nullptr, QLatin1String("YandexFotki Dialog")), d(new Private) { d->iface = iface; d->import = import; d->tmpDir = WSToolUtils::makeTemporaryDir("yandexfotki").absolutePath() + QLatin1Char('/'); d->widget = new YFWidget(this, d->iface, QLatin1String("Yandex.Fotki")); d->loginLabel = d->widget->getUserNameLabel(); d->headerLabel = d->widget->getHeaderLbl(); d->changeUserButton = d->widget->getChangeUserBtn(); d->newAlbumButton = d->widget->getNewAlbmBtn(); d->reloadAlbumsButton = d->widget->getReloadBtn(); d->albumsCombo = d->widget->getAlbumsCoB(); d->resizeCheck = d->widget->getResizeCheckBox(); d->dimensionSpin = d->widget->getDimensionSpB(); d->imageQualitySpin = d->widget->getImgQualitySpB(); d->imgList = d->widget->imagesList(); d->progressBar = d->widget->progressBar(); d->accessCombo = d->widget->accessCB(); d->hideOriginalCheck = d->widget->hideOriginalCB(); d->disableCommentsCheck = d->widget->disableCommentsCB(); d->adultCheck = d->widget->adultCB(); d->policyGroup = d->widget->policyGB(); d->albumsBox = d->widget->getAlbumBox(); connect(d->changeUserButton, SIGNAL(clicked()), this, SLOT(slotChangeUserClicked())); connect(d->newAlbumButton, SIGNAL(clicked()), this, SLOT(slotNewAlbumRequest()) ); connect(d->reloadAlbumsButton, SIGNAL(clicked()), this, SLOT(slotReloadAlbumsRequest()) ); setMainWidget(d->widget); d->widget->setMinimumSize(800, 600); // -- UI slots ----------------------------------------------------------------------- connect(startButton(), &QPushButton::clicked, this, &YFWindow::slotStartTransfer); connect(this, &WSToolDialog::cancelClicked, this, &YFWindow::slotCancelClicked); connect(this, &QDialog::finished, this, &YFWindow::slotFinished); // -- Talker slots ------------------------------------------------------------------- connect(&d->talker, SIGNAL(signalError()), this, SLOT(slotError())); connect(&d->talker, SIGNAL(signalGetSessionDone()), this, SLOT(slotGetSessionDone())); connect(&d->talker, SIGNAL(signalGetTokenDone()), this, SLOT(slotGetTokenDone())); connect(&d->talker, SIGNAL(signalGetServiceDone()), this, SLOT(slotGetServiceDone())); connect(&d->talker, SIGNAL(signalListAlbumsDone(QList)), this, SLOT(slotListAlbumsDone(QList))); connect(&d->talker, SIGNAL(signalListPhotosDone(QList)), this, SLOT(slotListPhotosDone(QList))); connect(&d->talker, SIGNAL(signalUpdatePhotoDone(YFPhoto&)), this, SLOT(slotUpdatePhotoDone(YFPhoto&))); connect(&d->talker, SIGNAL(signalUpdateAlbumDone()), this, SLOT(slotUpdateAlbumDone())); // read settings from file readSettings(); } YFWindow::~YFWindow() { reset(); WSToolUtils::removeTemporaryDir("yandexfotki"); delete d; } void YFWindow::reactivate() { d->imgList->loadImagesFromCurrentSelection(); reset(); authenticate(false); show(); } void YFWindow::reset() { d->talker.reset(); updateControls(true); updateLabels(); } void YFWindow::updateControls(bool val) { if (val) { if (d->talker.isAuthenticated()) { d->albumsBox->setEnabled(true); startButton()->setEnabled(true); } else { d->albumsBox->setEnabled(false); startButton()->setEnabled(false); } d->changeUserButton->setEnabled(true); setCursor(Qt::ArrowCursor); setRejectButtonMode(QDialogButtonBox::Close); } else { setCursor(Qt::WaitCursor); d->albumsBox->setEnabled(false); d->changeUserButton->setEnabled(false); startButton()->setEnabled(false); setRejectButtonMode(QDialogButtonBox::Cancel); } } void YFWindow::updateLabels() { QString urltext; QString logintext; if (d->talker.isAuthenticated()) { logintext = d->talker.login(); urltext = YFTalker::USERPAGE_URL.arg(d->talker.login()); d->albumsBox->setEnabled(true); } else { logintext = i18n("Unauthorized"); urltext = YFTalker::USERPAGE_DEFAULT_URL; d->albumsCombo->clear(); } d->loginLabel->setText(QString::fromLatin1("%1").arg(logintext)); d->headerLabel->setText(QString::fromLatin1( "

" "%2" "%3" "%4" "

") .arg(urltext) .arg(i18nc("Yandex.Fotki", "Y")) .arg(i18nc("Yandex.Fotki", "andex.")) .arg(i18nc("Yandex.Fotki", "Fotki"))); } void YFWindow::readSettings() { KConfig config; KConfigGroup grp = config.group("YandexFotki Settings"); d->talker.setLogin(grp.readEntry("login", "")); // don't store tokens in plaintext //d->talker.setToken(grp.readEntry("token", "")); if (grp.readEntry("Resize", false)) { d->resizeCheck->setChecked(true); d->dimensionSpin->setEnabled(true); d->imageQualitySpin->setEnabled(true); } else { d->resizeCheck->setChecked(false); d->dimensionSpin->setEnabled(false); d->imageQualitySpin->setEnabled(false); } d->dimensionSpin->setValue(grp.readEntry("Maximum Width", 1600)); d->imageQualitySpin->setValue(grp.readEntry("Image Quality", 85)); d->policyGroup->button(grp.readEntry("Sync policy", 0))->setChecked(true); } void YFWindow::writeSettings() { KConfig config; KConfigGroup grp = config.group("YandexFotki Settings"); grp.writeEntry("token", d->talker.token()); // don't store tokens in plaintext //grp.writeEntry("login", d->talker.login()); grp.writeEntry("Resize", d->resizeCheck->isChecked()); grp.writeEntry("Maximum Width", d->dimensionSpin->value()); grp.writeEntry("Image Quality", d->imageQualitySpin->value()); grp.writeEntry("Sync policy", d->policyGroup->checkedId()); } void YFWindow::slotChangeUserClicked() { // force authenticate window authenticate(true); } void YFWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } slotFinished(); e->accept(); } void YFWindow::slotFinished() { writeSettings(); reset(); } void YFWindow::slotCancelClicked() { d->talker.cancel(); updateControls(true); } /* void YFWindow::cancelProcessing() { d->talker.cancel(); d->transferQueue.clear(); d->imgList->processed(false); progressBar()->hide(); } */ void YFWindow::authenticate(bool forceAuthWindow) { // update credentials if (forceAuthWindow || d->talker.login().isNull() || d->talker.password().isNull()) { WSLoginDialog* const dlg = new WSLoginDialog(this, QLatin1String("Yandex.Fotki"), d->talker.login(), QString()); if (dlg->exec() == QDialog::Accepted) { d->talker.setLogin(dlg->login()); d->talker.setPassword(dlg->password()); } else { // don't change anything return; } delete dlg; } /* else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Checking old token..."; d->talker.checkToken(); return; } */ // if new credentials non-empty, authenticate if (!d->talker.login().isEmpty() && !d->talker.password().isEmpty()) { // cancel all tasks first reset(); // start authentication chain updateControls(false); d->talker.getService(); } else { // we don't have valid credentials, so cancel all transfers and reset reset(); } /* progressBar()->show(); progressBar()->setFormat(""); */ } void YFWindow::slotListPhotosDone(const QList & photosList) { if (d->import) { slotListPhotosDoneForDownload(photosList); } else { slotListPhotosDoneForUpload(photosList); } } void YFWindow::slotListPhotosDoneForDownload(const QList & photosList) { Q_UNUSED(photosList); updateControls(true); } void YFWindow::slotListPhotosDoneForUpload(const QList & photosList) { updateControls(true); QMap dups; int i = 0; - foreach(const YFPhoto& photo, photosList) + foreach (const YFPhoto& photo, photosList) { dups.insert(photo.urn(), i); i++; } YFWidget::UpdatePolicy policy = static_cast(d->policyGroup->checkedId()); const YFPhoto::Access access = static_cast( d->accessCombo->itemData(d->accessCombo->currentIndex()).toInt()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << ""; qCDebug(DIGIKAM_WEBSERVICES_LOG) << "----"; d->transferQueue.clear(); - foreach(const QUrl& url, d->imgList->imageUrls(true)) + foreach (const QUrl& url, d->imgList->imageUrls(true)) { DItemInfo info(d->iface->itemInfo(url)); // check if photo alredy uploaded int oldPhotoId = -1; if (d->meta.load(url.toLocalFile())) { QString localId = d->meta.getXmpTagString(d->XMP_SERVICE_ID); oldPhotoId = dups.value(localId, -1); } // get tags QStringList tags = info.tagsPath(); bool updateFile = true; QSet oldtags; if (oldPhotoId != -1) { if (policy == YFWidget::UpdatePolicy::POLICY_SKIP) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SKIP: " << url; continue; } // old photo copy d->transferQueue.push(photosList[oldPhotoId]); if (policy == YFWidget::UpdatePolicy::POLICY_UPDATE_MERGE) { - foreach(const QString& t, d->transferQueue.top().tags) + foreach (const QString& t, d->transferQueue.top().tags) { oldtags.insert(t); } } if (policy != YFWidget::UpdatePolicy::POLICY_ADDNEW) { updateFile = false; } } else { // empty photo d->transferQueue.push(YFPhoto()); } YFPhoto& photo = d->transferQueue.top(); // TODO: updateFile is not used photo.setOriginalUrl(url.toLocalFile()); photo.setTitle(info.name()); photo.setSummary(info.comment()); photo.setAccess(access); photo.setHideOriginal(d->hideOriginalCheck->isChecked()); photo.setDisableComments(d->disableCommentsCheck->isChecked()); // adult flag can't be removed, API restrictions if (!photo.isAdult()) photo.setAdult(d->adultCheck->isChecked()); - foreach(const QString& t, tags) + foreach (const QString& t, tags) { if (!oldtags.contains(t)) { photo.tags.append(t); } } if (updateFile) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "METADATA + IMAGE: " << url; } else { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "METADATA: " << url; } } if (d->transferQueue.isEmpty()) { return; // nothing to do } qCDebug(DIGIKAM_WEBSERVICES_LOG) << "----"; qCDebug(DIGIKAM_WEBSERVICES_LOG) << ""; updateControls(false); updateNextPhoto(); } void YFWindow::updateNextPhoto() { // select only one image from stack while (!d->transferQueue.isEmpty()) { YFPhoto& photo = d->transferQueue.top(); if (!photo.originalUrl().isNull()) { QImage image = PreviewLoadThread::loadHighQualitySynchronously(photo.originalUrl()).copyQImage(); if (image.isNull()) { image.load(photo.originalUrl()); } photo.setLocalUrl(d->tmpDir + QFileInfo(photo.originalUrl()) .baseName() .trimmed() + QLatin1String(".jpg")); bool prepared = false; if (!image.isNull()) { // get temporary file name // rescale image if requested int maxDim = d->dimensionSpin->value(); if (d->resizeCheck->isChecked() && (image.width() > maxDim || image.height() > maxDim)) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim; image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation); } // copy meta data to temporary image if (image.save(photo.localUrl(), "JPEG", d->imageQualitySpin->value())) { if (d->meta.load(photo.originalUrl())) { d->meta.setItemDimensions(image.size()); d->meta.setItemOrientation(MetaEngine::ORIENTATION_NORMAL); d->meta.setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY); d->meta.save(photo.localUrl(), true); prepared = true; } } } if (!prepared) { if (QMessageBox::question(this, i18n("Processing Failed"), i18n("Failed to prepare image %1\n" "Do you want to continue?", photo.originalUrl())) != QMessageBox::Yes) { // stop uploading d->transferQueue.clear(); continue; } else { d->transferQueue.pop(); continue; } } } const YandexFotkiAlbum& album = d->talker.albums().at(d->albumsCombo->currentIndex()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << photo.originalUrl(); d->talker.updatePhoto(photo, album); return; } updateControls(true); QMessageBox::information(this, QString(), i18n("Images have been uploaded")); return; } void YFWindow::slotNewAlbumRequest() { YandexFotkiAlbum album; QPointer dlg = new YFNewAlbumDlg(this, album); if (dlg->exec() == QDialog::Accepted) { updateControls(false); d->talker.updateAlbum(album); } delete dlg; } void YFWindow::slotReloadAlbumsRequest() { updateControls(false); d->talker.listAlbums(); } void YFWindow::slotStartTransfer() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotStartTransfer invoked"; if (d->albumsCombo->currentIndex() == -1 || d->albumsCombo->count() == 0) { QMessageBox::information(this, QString(), i18n("Please select album first")); return; } // TODO: import support if (!d->import) { // list photos of the album, then start upload const YandexFotkiAlbum& album = d->talker.albums().at(d->albumsCombo->currentIndex()); qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Album selected" << album; updateControls(false); d->talker.listPhotos(album); } } void YFWindow::slotError() { switch (d->talker.state()) { case YFTalker::STATE_GETSESSION_ERROR: QMessageBox::critical(this, QString(), i18n("Session error")); break; case YFTalker::STATE_GETTOKEN_ERROR: QMessageBox::critical(this, QString(), i18n("Token error")); break; case YFTalker::STATE_INVALID_CREDENTIALS: QMessageBox::critical(this, QString(), i18n("Invalid credentials")); // authenticate(true); break; case YFTalker::STATE_GETSERVICE_ERROR: QMessageBox::critical(this, QString(), i18n("Cannot get service document")); break; /* case YFTalker::STATE_CHECKTOKEN_INVALID: // remove old expired token qCDebug(DIGIKAM_WEBSERVICES_LOG) << "CheckToken invalid"; d->talker.setToken(QString()); // don't say anything, simple show new auth window authenticate(true); break; */ case YFTalker::STATE_LISTALBUMS_ERROR: d->albumsCombo->clear(); QMessageBox::critical(this, QString(), i18n("Cannot list albums")); break; case YFTalker::STATE_LISTPHOTOS_ERROR: QMessageBox::critical(this, QString(), i18n("Cannot list photos")); break; case YFTalker::STATE_UPDATEALBUM_ERROR: QMessageBox::critical(this, QString(), i18n("Cannot update album info")); break; case YFTalker::STATE_UPDATEPHOTO_FILE_ERROR: case YFTalker::STATE_UPDATEPHOTO_INFO_ERROR: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UpdatePhotoError"; if (QMessageBox::question(this, i18n("Uploading Failed"), i18n("Failed to upload image %1\n" "Do you want to continue?", d->transferQueue.top().originalUrl())) != QMessageBox::Yes) { // clear upload stack d->transferQueue.clear(); } else { // cancel current operation d->talker.cancel(); // remove only bad image d->transferQueue.pop(); // and try next updateNextPhoto(); return; } break; default: qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Unhandled error" << d->talker.state(); QMessageBox::critical(this, QString(), i18n("Unknown error")); } // cancel current operation d->talker.cancel(); updateControls(true); } void YFWindow::slotGetServiceDone() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetService Done"; d->talker.getSession(); } void YFWindow::slotGetSessionDone() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "GetSession Done"; d->talker.getToken(); } void YFWindow::slotGetTokenDone() { updateLabels(); slotReloadAlbumsRequest(); } void YFWindow::slotListAlbumsDone(const QList& albumsList) { d->albumsCombo->clear(); - foreach(const YandexFotkiAlbum& album, albumsList) + foreach (const YandexFotkiAlbum& album, albumsList) { QString albumIcon; if (album.isProtected()) { albumIcon = QLatin1String("folder-locked"); } else { albumIcon = QLatin1String("folder-image"); } d->albumsCombo->addItem(QIcon::fromTheme(albumIcon), album.toString()); } d->albumsCombo->setEnabled(true); updateControls(true); } void YFWindow::slotUpdatePhotoDone(YFPhoto& photo) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "photoUploaded" << photo; if (d->meta.supportXmp() && d->meta.canWriteXmp(photo.originalUrl()) && d->meta.load(photo.originalUrl())) { // ignore errors here if (d->meta.setXmpTagString(d->XMP_SERVICE_ID, photo.urn()) && d->meta.save(photo.originalUrl())) { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "MARK: " << photo.originalUrl(); } } d->transferQueue.pop(); updateNextPhoto(); } void YFWindow::slotUpdateAlbumDone() { qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Album created"; d->albumsCombo->clear(); d->talker.listAlbums(); } } // namespace DigikamGenericYFPlugin diff --git a/core/libs/album/engine/albumthumbnailloader.cpp b/core/libs/album/engine/albumthumbnailloader.cpp index 36d951676a..489627ee5f 100644 --- a/core/libs/album/engine/albumthumbnailloader.cpp +++ b/core/libs/album/engine/albumthumbnailloader.cpp @@ -1,527 +1,527 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-04-14 * Description : Load and cache tag thumbnails * * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2015 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "albumthumbnailloader.h" // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "album.h" #include "albummanager.h" #include "applicationsettings.h" #include "iteminfo.h" #include "metaenginesettings.h" #include "thumbnailloadthread.h" #include "thumbnailsize.h" #include "facetagseditor.h" #include "thumbnailcreator.h" namespace Digikam { typedef QMap, QList > IdAlbumMap; typedef QMap AlbumThumbnailMap; class Q_DECL_HIDDEN AlbumThumbnailLoaderCreator { public: AlbumThumbnailLoader object; }; Q_GLOBAL_STATIC(AlbumThumbnailLoaderCreator, creator) // --------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN AlbumThumbnailLoader::Private { public: explicit Private() { iconSize = ApplicationSettings::instance()->getTreeViewIconSize(); minBlendSize = 20; iconAlbumThumbThread = nullptr; iconTagThumbThread = nullptr; } int iconSize; int minBlendSize; ThumbnailLoadThread* iconTagThumbThread; ThumbnailLoadThread* iconAlbumThumbThread; IdAlbumMap idAlbumMap; AlbumThumbnailMap thumbnailMap; QCache, QPixmap> iconCache; }; bool operator<(const ThumbnailIdentifier& a, const ThumbnailIdentifier& b) { if (a.id || b.id) { return a.id < b.id; } else { return a.filePath < b.filePath; } } AlbumThumbnailLoader* AlbumThumbnailLoader::instance() { return &creator->object; } AlbumThumbnailLoader::AlbumThumbnailLoader() : d(new Private) { connect(this, SIGNAL(signalDispatchThumbnailInternal(int,QPixmap)), this, SLOT(slotDispatchThumbnailInternal(int,QPixmap))); connect(AlbumManager::instance(), SIGNAL(signalAlbumIconChanged(Album*)), this, SLOT(slotIconChanged(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotIconChanged(Album*))); } AlbumThumbnailLoader::~AlbumThumbnailLoader() { delete d->iconTagThumbThread; delete d->iconAlbumThumbThread; delete d; } void AlbumThumbnailLoader::cleanUp() { delete d->iconTagThumbThread; d->iconTagThumbThread = nullptr; delete d->iconAlbumThumbThread; d->iconAlbumThumbThread = nullptr; } QPixmap AlbumThumbnailLoader::getStandardTagIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("tag"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardTagRootIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("document-open"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardTagIcon(TAlbum* const album, RelativeSize relativeSize) { if (album->isRoot()) { return getStandardTagRootIcon(relativeSize); } else { return getStandardTagIcon(relativeSize); } } QPixmap AlbumThumbnailLoader::getNewTagIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("tag-new"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardAlbumIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("folder"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardAlbumTrashIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("user-trash"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardAlbumRootIcon(RelativeSize relativeSize) { return loadIcon(QLatin1String("folder-pictures"), computeIconSize(relativeSize)); } QPixmap AlbumThumbnailLoader::getStandardAlbumIcon(PAlbum* const album, RelativeSize relativeSize) { if (album->isRoot() || album->isAlbumRoot()) { return getStandardAlbumRootIcon(relativeSize); } else if (album->isTrashAlbum()) { return getStandardAlbumTrashIcon(); } else { return getStandardAlbumIcon(relativeSize); } } int AlbumThumbnailLoader::computeIconSize(RelativeSize relativeSize) const { if (relativeSize == SmallerSize) { // when size was 32 smaller was 20. Scale. return lround(20.0 / 32.0 * (double)d->iconSize); } return d->iconSize; } QPixmap AlbumThumbnailLoader::loadIcon(const QString& name, int size) const { QPixmap* pix = d->iconCache[qMakePair(name, size)]; if (!pix) { d->iconCache.insert(qMakePair(name, size), new QPixmap(QIcon::fromTheme(name).pixmap(size))); pix = d->iconCache[qMakePair(name, size)]; } return (*pix); // ownership of the pointer is kept by the icon cache. } bool AlbumThumbnailLoader::getTagThumbnail(TAlbum* const album, QPixmap& icon) { if (album->iconId() && d->iconSize > d->minBlendSize) { addUrl(album, album->iconId()); icon = QPixmap(); return true; } else if (!album->icon().isEmpty()) { icon = loadIcon(album->icon(), d->iconSize); return false; } icon = QPixmap(); return false; } QPixmap AlbumThumbnailLoader::getTagThumbnailDirectly(TAlbum* const album) { if (album->iconId() && d->iconSize > d->minBlendSize) { // icon cached? AlbumThumbnailMap::const_iterator it = d->thumbnailMap.constFind(album->globalID()); if (it != d->thumbnailMap.constEnd()) { return *it; } addUrl(album, album->iconId()); } else if (!album->icon().isEmpty()) { QPixmap pixmap = loadIcon(album->icon(), d->iconSize); return pixmap; } return getStandardTagIcon(album); } bool AlbumThumbnailLoader::getAlbumThumbnail(PAlbum* const album) { if (album->iconId() && d->iconSize > d->minBlendSize) { addUrl(album, album->iconId()); } else { return false; } return true; } QPixmap AlbumThumbnailLoader::getAlbumThumbnailDirectly(PAlbum* const album) { if (album->iconId() && d->iconSize > d->minBlendSize) { // icon cached? AlbumThumbnailMap::const_iterator it = d->thumbnailMap.constFind(album->globalID()); if (it != d->thumbnailMap.constEnd()) { return *it; } // schedule for loading addUrl(album, album->iconId()); } return getStandardAlbumIcon(album); } void AlbumThumbnailLoader::addUrl(Album* const album, qlonglong id) { // First check cached thumbnails. // We use a private cache which is actually a map to be sure to cache _all_ album thumbnails. // At startup, this is not relevant, as the views will add their requests in a row. // This is to speed up context menu and IE imagedescedit AlbumThumbnailMap::const_iterator ttit = d->thumbnailMap.constFind(album->globalID()); if (ttit != d->thumbnailMap.constEnd()) { // It is not necessary to return cached icon asynchronously - they could be // returned by getTagThumbnail already - but this would make the API // less elegant, it feels much better this way. emit signalDispatchThumbnailInternal(album->globalID(), *ttit); return; } //Finding face rect to identify correct tag QRect faceRect = QRect(); if (album->type() == Album::TAG && static_cast(album)->hasProperty(TagPropertyName::person())) { QList faces = FaceTagsEditor().databaseFaces(id); - foreach(const FaceTagsIface& face, faces) { + foreach (const FaceTagsIface& face, faces) { if (face.tagId() == album->id()) { faceRect = face.region().toRect(); } } } //Simple way to put QRect into QMap QString faceRectStr = QString(QLatin1String("%1%2%3%4")) .arg(faceRect.x()).arg(faceRect.y()).arg(faceRect.right()).arg(faceRect.bottom()); // Check if the URL has already been added IdAlbumMap::iterator it = d->idAlbumMap.find(QPair(id, faceRectStr)); if (it == d->idAlbumMap.end()) { // use two threads so that tag and album thumbnails are loaded // in parallel and not first album, then tag thumbnails if (album->type() == Album::TAG) { if (!d->iconTagThumbThread) { d->iconTagThumbThread = new ThumbnailLoadThread(); d->iconTagThumbThread->setThumbnailSize(d->iconSize); d->iconTagThumbThread->setSendSurrogatePixmap(false); connect(d->iconTagThumbThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), SLOT(slotGotThumbnailFromIcon(LoadingDescription,QPixmap)), Qt::QueuedConnection); } if (static_cast(album)->hasProperty(TagPropertyName::person())) { d->iconTagThumbThread->find(ItemInfo::thumbnailIdentifier(id), faceRect); } else { d->iconTagThumbThread->find(ItemInfo::thumbnailIdentifier(id)); } } else { if (!d->iconAlbumThumbThread) { d->iconAlbumThumbThread = new ThumbnailLoadThread(); d->iconAlbumThumbThread->setThumbnailSize(d->iconSize); d->iconAlbumThumbThread->setSendSurrogatePixmap(false); connect(d->iconAlbumThumbThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), SLOT(slotGotThumbnailFromIcon(LoadingDescription,QPixmap)), Qt::QueuedConnection); } d->iconAlbumThumbThread->find(ItemInfo::thumbnailIdentifier(id)); } // insert new entry to map, add album globalID QList &list = d->idAlbumMap[QPair(id, faceRectStr)]; list.removeAll(album->globalID()); list.append(album->globalID()); } else { // only add album global ID to list which is already inserted in map (*it).removeAll(album->globalID()); (*it).append(album->globalID()); } } void AlbumThumbnailLoader::setThumbnailSize(int size) { if (d->iconSize == size) { return; } d->iconSize = size; // clear task list d->idAlbumMap.clear(); // clear cached thumbnails d->thumbnailMap.clear(); if (d->iconAlbumThumbThread) { d->iconAlbumThumbThread->stopLoading(); d->iconAlbumThumbThread->setThumbnailSize(size); } if (d->iconTagThumbThread) { d->iconTagThumbThread->stopLoading(); d->iconTagThumbThread->setThumbnailSize(size); } emit signalReloadThumbnails(); } int AlbumThumbnailLoader::thumbnailSize() const { return d->iconSize; } void AlbumThumbnailLoader::slotGotThumbnailFromIcon(const LoadingDescription& loadingDescription, const QPixmap& thumbnail) { // We need to find all albums for which the given url has been requested, // and emit a signal for each album. QRect faceRect = QRect(); if (loadingDescription.previewParameters.extraParameter.type() == QVariant::Rect){ faceRect = loadingDescription.previewParameters.extraParameter.toRect(); } //Simple way to put QRect into QMap QString faceRectStr = QString(QLatin1String("%1%2%3%4")) .arg(faceRect.x()).arg(faceRect.y()).arg(faceRect.right()).arg(faceRect.bottom()); ThumbnailIdentifier id = loadingDescription.thumbnailIdentifier(); IdAlbumMap::iterator it = d->idAlbumMap.find(QPair(id.id, faceRectStr)); if (it != d->idAlbumMap.end()) { AlbumManager* const manager = AlbumManager::instance(); if (thumbnail.isNull()) { // Loading failed for (QList::const_iterator vit = (*it).constBegin(); vit != (*it).constEnd(); ++vit) { Album* const album = manager->findAlbum(*vit); if (album) { emit signalFailed(album); } } } else { // Loading succeeded for (QList::const_iterator vit = (*it).constBegin(); vit != (*it).constEnd(); ++vit) { // look up with global id Album* const album = manager->findAlbum(*vit); if (album) { d->thumbnailMap.insert(album->globalID(), thumbnail); emit signalThumbnail(album, thumbnail); } } } d->idAlbumMap.erase(it); } } void AlbumThumbnailLoader::slotDispatchThumbnailInternal(int albumID, const QPixmap& thumbnail) { // for cached thumbnails AlbumManager* const manager = AlbumManager::instance(); Album* const album = manager->findAlbum(albumID); if (album) { if (thumbnail.isNull()) { emit signalFailed(album); } else { emit signalThumbnail(album, thumbnail); } } } void AlbumThumbnailLoader::slotIconChanged(Album* album) { if (!album || (album->type() != Album::TAG && album->type() != Album::PHYSICAL)) { return; } d->thumbnailMap.remove(album->globalID()); } /* * This code is maximally inefficient QImage AlbumThumbnailLoader::getAlbumPreviewDirectly(PAlbum* const album, int size) { if (album->iconId()) { ThumbnailLoadThread* const thread = new ThumbnailLoadThread; thread->setPixmapRequested(false); thread->setThumbnailSize(size); ThumbnailImageCatcher* const catcher = new ThumbnailImageCatcher(thread); catcher->setActive(true); catcher->thread()->find(ThumbnailIdentifier(album->iconId()); catcher->enqueue(); QList images = catcher->waitForThumbnails(); catcher->setActive(false); delete thread; delete catcher; if (!images.isEmpty()) return images[0]; } return loadIcon("folder", size).toImage(); } */ } // namespace Digikam diff --git a/core/libs/database/collection/collectionscanner_scan.cpp b/core/libs/database/collection/collectionscanner_scan.cpp index a5fe30a185..bdafdf5ddf 100644 --- a/core/libs/database/collection/collectionscanner_scan.cpp +++ b/core/libs/database/collection/collectionscanner_scan.cpp @@ -1,1040 +1,1040 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-03-21 * Description : Collection scanning to database - scan operations. * * Copyright (C) 2005-2006 by Tom Albers * Copyright (C) 2007-2011 by Marcel Wiesweg * Copyright (C) 2009-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "collectionscanner_p.h" namespace Digikam { void CollectionScanner::completeScan() { QTime time; time.start(); emit startCompleteScan(); { // lock database CoreDbTransaction transaction; mainEntryPoint(true); d->resetRemovedItemsTime(); } //TODO: Implement a mechanism to watch for album root changes while we keep this list QList allLocations = CollectionManager::instance()->allAvailableLocations(); if (d->wantSignals && d->needTotalFiles) { // count for progress info int count = 0; foreach (const CollectionLocation& location, allLocations) { count += countItemsInFolder(location.albumRootPath()); } emit totalFilesToScan(count); } if (!d->checkObserver()) { emit cancelled(); return; } // if we have no hints to follow, clean up all stale albums if (!d->hints || !d->hints->hasAlbumHints()) { CoreDbAccess().db()->deleteStaleAlbums(); } scanForStaleAlbums(allLocations); if (!d->checkObserver()) { emit cancelled(); return; } if (d->wantSignals) { emit startScanningAlbumRoots(); } foreach (const CollectionLocation& location, allLocations) { scanAlbumRoot(location); } // do not continue to clean up without a complete scan! if (!d->checkObserver()) { emit cancelled(); return; } if (d->deferredFileScanning) { qCDebug(DIGIKAM_DATABASE_LOG) << "Complete scan (file scanning deferred) took:" << time.elapsed() << "msecs."; emit finishedCompleteScan(); return; } CoreDbTransaction transaction; completeScanCleanupPart(); qCDebug(DIGIKAM_DATABASE_LOG) << "Complete scan took:" << time.elapsed() << "msecs."; } void CollectionScanner::finishCompleteScan(const QStringList& albumPaths) { emit startCompleteScan(); { // lock database CoreDbTransaction transaction; mainEntryPoint(true); d->resetRemovedItemsTime(); } if (!d->checkObserver()) { emit cancelled(); return; } if (d->wantSignals) { emit startScanningAlbumRoots(); } // remove subalbums from list if parent album is already contained QStringList sortedPaths = albumPaths; std::sort(sortedPaths.begin(), sortedPaths.end()); QStringList::iterator it, it2; for (it = sortedPaths.begin() ; it != sortedPaths.end() ; ) { // remove all following entries as long as they have the same beginning (= are subalbums) for (it2 = it + 1 ; it2 != sortedPaths.end() && it2->startsWith(*it) ; ) { it2 = sortedPaths.erase(it2); } it = it2; } if (d->wantSignals && d->needTotalFiles) { // count for progress info int count = 0; foreach (const QString& path, sortedPaths) { count += countItemsInFolder(path); } emit totalFilesToScan(count); } foreach (const QString& path, sortedPaths) { CollectionLocation location = CollectionManager::instance()->locationForPath(path); QString album = CollectionManager::instance()->album(path); if (album == QLatin1String("/")) { scanAlbumRoot(location); } else { scanAlbum(location, album); } } // do not continue to clean up without a complete scan! if (!d->checkObserver()) { emit cancelled(); return; } CoreDbTransaction transaction; completeScanCleanupPart(); } void CollectionScanner::completeScanCleanupPart() { completeHistoryScanning(); updateRemovedItemsTime(); // Items may be set to status removed, without being definitely deleted. // This deletion shall be done after a certain time, as checked by checkedDeleteRemoved if (checkDeleteRemoved()) { // Mark items that are old enough and have the status trashed as obsolete // Only do this in a complete scan! CoreDbAccess access; QList trashedItems = access.db()->getImageIds(DatabaseItem::Status::Trashed); foreach (const qlonglong& item, trashedItems) { access.db()->setItemStatus(item, DatabaseItem::Status::Obsolete); } resetDeleteRemovedSettings(); } else { // increment the count of complete scans during which removed items were not deleted incrementDeleteRemovedCompleteScanCount(); } markDatabaseAsScanned(); emit finishedCompleteScan(); } void CollectionScanner::partialScan(const QString& filePath) { QString albumRoot = CollectionManager::instance()->albumRootPath(filePath); QString album = CollectionManager::instance()->album(filePath); partialScan(albumRoot, album); } void CollectionScanner::partialScan(const QString& albumRoot, const QString& album) { if (albumRoot.isNull() || album.isEmpty()) { // If you want to scan the album root, pass "/" qCWarning(DIGIKAM_DATABASE_LOG) << "partialScan(QString, QString) called with invalid values"; return; } /* if (CoreDbAccess().backend()->isInTransaction()) { // Install ScanController::instance()->suspendCollectionScan around your CoreDbTransaction qCDebug(DIGIKAM_DATABASE_LOG) << "Detected an active database transaction when starting a collection scan. " "Please report this error."; return; } */ mainEntryPoint(false); d->resetRemovedItemsTime(); CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot); if (location.isNull()) { qCWarning(DIGIKAM_DATABASE_LOG) << "Did not find a CollectionLocation for album root path " << albumRoot; return; } // if we have no hints to follow, clean up all stale albums // Hint: Rethink with next major db update if (!d->hints || !d->hints->hasAlbumHints()) { CoreDbAccess().db()->deleteStaleAlbums(); } // Usually, we can restrict stale album scanning to our own location. // But when there are album hints from a second location to this location, // also scan the second location QSet locationIdsToScan; locationIdsToScan << location.id(); if (d->hints) { QReadLocker locker(&d->hints->lock); QHash::const_iterator it; for (it = d->hints->albumHints.constBegin() ; it != d->hints->albumHints.constEnd() ; ++it) { if (it.key().albumRootId == location.id()) { locationIdsToScan << it.key().albumRootId; } } } scanForStaleAlbums(locationIdsToScan.toList()); if (!d->checkObserver()) { emit cancelled(); return; } if (album == QLatin1String("/")) { scanAlbumRoot(location); } else { scanAlbum(location, album); } finishHistoryScanning(); if (!d->checkObserver()) { emit cancelled(); return; } updateRemovedItemsTime(); } qlonglong CollectionScanner::scanFile(const QString& filePath, FileScanMode mode) { QFileInfo info(filePath); QString dirPath = info.path(); // strip off filename QString albumRoot = CollectionManager::instance()->albumRootPath(dirPath); if (albumRoot.isNull()) { return -1; } QString album = CollectionManager::instance()->album(dirPath); return scanFile(albumRoot, album, info.fileName(), mode); } qlonglong CollectionScanner::scanFile(const QString& albumRoot, const QString& album, const QString& fileName, FileScanMode mode) { if (album.isEmpty() || fileName.isEmpty()) { qCWarning(DIGIKAM_DATABASE_LOG) << "scanFile(QString, QString, QString) called with empty album or empty filename"; return -1; } CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot); if (location.isNull()) { qCWarning(DIGIKAM_DATABASE_LOG) << "Did not find a CollectionLocation for album root path " << albumRoot; return -1; } QDir dir(location.albumRootPath() + album); QFileInfo fi(dir, fileName); if (!fi.exists()) { qCWarning(DIGIKAM_DATABASE_LOG) << "File given to scan does not exist" << albumRoot << album << fileName; return -1; } int albumId = checkAlbum(location, album); qlonglong imageId = CoreDbAccess().db()->getImageId(albumId, fileName); imageId = scanFile(fi, albumId, imageId, mode); return imageId; } void CollectionScanner::scanFile(const ItemInfo& info, FileScanMode mode) { if (info.isNull()) { return; } QFileInfo fi(info.filePath()); scanFile(fi, info.albumId(), info.id(), mode); } qlonglong CollectionScanner::scanFile(const QFileInfo& fi, int albumId, qlonglong imageId, FileScanMode mode) { mainEntryPoint(false); if (imageId == -1) { switch (mode) { case NormalScan: case ModifiedScan: imageId = scanNewFile(fi, albumId); break; case Rescan: imageId = scanNewFileFullScan(fi, albumId); break; } } else { ItemScanInfo scanInfo = CoreDbAccess().db()->getItemScanInfo(imageId); switch (mode) { case NormalScan: scanFileNormal(fi, scanInfo); break; case ModifiedScan: scanModifiedFile(fi, scanInfo); break; case Rescan: rescanFile(fi, scanInfo); break; } } finishHistoryScanning(); return imageId; } void CollectionScanner::scanAlbumRoot(const CollectionLocation& location) { if (d->wantSignals) { emit startScanningAlbumRoot(location.albumRootPath()); } /* QDir dir(location.albumRootPath()); QStringList fileList(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)); for (QStringList::iterator fileIt = fileList.begin(); fileIt != fileList.end(); ++fileIt) { scanAlbum(location, '/' + (*fileIt)); } */ // scan album that covers the root directory of this album root, // all contained albums, and their subalbums recursively. scanAlbum(location, QLatin1String("/")); if (d->wantSignals) { emit finishedScanningAlbumRoot(location.albumRootPath()); } } void CollectionScanner::scanForStaleAlbums(const QList& locations) { QList locationIdsToScan; foreach (const CollectionLocation& location, locations) { locationIdsToScan << location.id(); } scanForStaleAlbums(locationIdsToScan); } void CollectionScanner::scanForStaleAlbums(const QList& locationIdsToScan) { if (d->wantSignals) { emit startScanningForStaleAlbums(); } QList albumList = CoreDbAccess().db()->getAlbumShortInfos(); QList toBeDeleted; /* // See bug #231598 QHash albumRoots; - foreach(const CollectionLocation& location, locations) + foreach (const CollectionLocation& location, locations) { albumRoots[location.id()] = location; } */ QList::const_iterator it; for (it = albumList.constBegin() ; it != albumList.constEnd() ; ++it) { if (!locationIdsToScan.contains((*it).albumRootId)) { continue; } CollectionLocation location = CollectionManager::instance()->locationForAlbumRootId((*it).albumRootId); // Only handle albums on available locations if (location.isAvailable()) { QFileInfo fileInfo(location.albumRootPath() + (*it).relativePath); bool dirExist = (fileInfo.exists() && fileInfo.isDir()); if (!(*it).relativePath.endsWith(QLatin1Char('/'))) { QDir dir(fileInfo.dir()); dirExist = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot) .contains(fileInfo.fileName()); } // let digikam think that ignored directories got deleted // (if they already exist in the database, this will delete them) if (!dirExist || d->ignoreDirectory.contains(fileInfo.fileName())) { toBeDeleted << (*it).id; d->scannedAlbums << (*it).id; } } } // At this point, it is important to handle album renames. // We can still copy over album attributes later, but we cannot identify // the former album of removed images. // Just renaming the album is also much cheaper than rescanning all files. if (!toBeDeleted.isEmpty() && d->hints) { // shallow copy for reading without caring for locks QHash albumHints; { QReadLocker locker(&d->hints->lock); albumHints = d->hints->albumHints; } // go through all album copy/move hints QHash::const_iterator it; int toBeDeletedIndex; for (it = albumHints.constBegin() ; it != albumHints.constEnd() ; ++it) { // if the src entry of a hint is found in toBeDeleted, we have a move/rename, no copy. Handle these here. toBeDeletedIndex = toBeDeleted.indexOf(it.value().albumId); // We must double check that not, for some reason, the target album has already been scanned. QList::const_iterator it2; for (it2 = albumList.constBegin() ; it2 != albumList.constEnd() ; ++it2) { if (it2->albumRootId == it.key().albumRootId && it2->relativePath == it.key().relativePath) { toBeDeletedIndex = -1; break; } } if (toBeDeletedIndex != -1) { // check for existence of target CollectionLocation location = CollectionManager::instance()->locationForAlbumRootId(it.key().albumRootId); if (location.isAvailable()) { QFileInfo fileInfo(location.albumRootPath() + it.key().relativePath); bool dirExist = (fileInfo.exists() && fileInfo.isDir()); if (!it.key().relativePath.endsWith(QLatin1Char('/'))) { QDir dir(fileInfo.dir()); dirExist = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot) .contains(fileInfo.fileName()); } // Make sure ignored directories are not used in renaming operations if (dirExist && d->ignoreDirectory.contains(fileInfo.fileName())) { // Just set a new root/relativePath to the album. Further scanning will care for all cases or error. CoreDbAccess().db()->renameAlbum(it.value().albumId, it.key().albumRootId, it.key().relativePath); // No need any more to delete the album toBeDeleted.removeAt(toBeDeletedIndex); } } } } } safelyRemoveAlbums(toBeDeleted); if (d->wantSignals) { emit finishedScanningForStaleAlbums(); } } void CollectionScanner::scanAlbum(const CollectionLocation& location, const QString& album) { // + Adds album if it does not yet exist in the db. // + Recursively scans subalbums of album. // + Adds files if they do not yet exist in the db. // + Marks stale files as removed QDir dir(location.albumRootPath() + album); if (!dir.exists() || !dir.isReadable()) { qCWarning(DIGIKAM_DATABASE_LOG) << "Folder does not exist or is not readable: " << dir.path(); return; } if (d->wantSignals) { emit startScanningAlbum(location.albumRootPath(), album); } int albumID = checkAlbum(location, album); MetaEngineSettingsContainer settings = MetaEngineSettings::instance()->settings(); const QList& scanInfos = CoreDbAccess().db()->getItemScanInfos(albumID); // create a QHash filename -> index in list QHash fileNameIndexHash; QSet itemIdSet; for (int i = 0 ; i < scanInfos.size() ; ++i) { fileNameIndexHash[scanInfos.at(i).itemName] = i; itemIdSet << scanInfos.at(i).id; } const QStringList& list = dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsLast); const QString xmpExt(QLatin1String(".xmp")); int counter = -1; foreach (const QString& entry, list) { if (!d->checkObserver()) { return; // return directly, do not go to cleanup code after loop! } ++counter; if (d->wantSignals && counter && (counter % 100 == 0)) { emit scannedFiles(counter); counter = 0; } QFileInfo info(dir, entry); if (info.isFile()) { // filter with name filter QString suffix(info.suffix().toLower()); if (!d->nameFilters.contains(suffix)) { continue; } int index = fileNameIndexHash.value(info.fileName(), -1); if (index != -1) { // mark item as "seen" itemIdSet.remove(scanInfos.at(index).id); bool hasSidecar = (settings.useXMPSidecar4Reading && (list.contains(info.fileName() + xmpExt) || list.contains(info.completeBaseName() + xmpExt))); scanFileNormal(info, scanInfos.at(index), hasSidecar); } else if (info.completeSuffix().contains(QLatin1String("digikamtempfile."))) { // ignore temp files we created ourselves continue; } else { //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding item " << info.fileName(); scanNewFile(info, albumID); // emit signals for scanned files with much higher granularity if (d->wantSignals && counter && (counter % 2 == 0)) { emit scannedFiles(counter); counter = 0; } } } else if (info.isDir()) { #ifdef Q_OS_WIN //Hide album that starts with a dot, as under Linux. if (info.fileName().startsWith(QLatin1Char('.'))) { continue; } #endif if (d->ignoreDirectory.contains(info.fileName())) { continue; } QString subAlbum = album; if (subAlbum != QLatin1String("/")) { subAlbum += QLatin1Char('/'); } scanAlbum(location, subAlbum + info.fileName()); } } if (d->wantSignals && counter) { emit scannedFiles(counter); } // Mark items in the db which we did not see on disk. if (!itemIdSet.isEmpty()) { QList ids = itemIdSet.toList(); CoreDbOperationGroup group; CoreDbAccess().db()->removeItems(ids, QList() << albumID); itemsWereRemoved(ids); } // mark album as scanned d->scannedAlbums << albumID; if (d->wantSignals) { emit finishedScanningAlbum(location.albumRootPath(), album, list.count()); } } void CollectionScanner::scanFileNormal(const QFileInfo& fi, const ItemScanInfo& scanInfo, bool checkSidecar) { bool hasAnyHint = d->hints && d->hints->hasAnyNormalHint(scanInfo.id); // if the date is null, this signals a full rescan if (scanInfo.modificationDate.isNull() || (hasAnyHint && d->hints->hasRescanHint(scanInfo.id))) { if (hasAnyHint) { QWriteLocker locker(&d->hints->lock); d->hints->rescanItemHints.remove(scanInfo.id); } rescanFile(fi, scanInfo); return; } else if (hasAnyHint && d->hints->hasModificationHint(scanInfo.id)) { { QWriteLocker locker(&d->hints->lock); d->hints->modifiedItemHints.remove(scanInfo.id); } scanModifiedFile(fi, scanInfo); return; } else if (hasAnyHint) // metadata adjustment hints { if (d->hints->hasMetadataAboutToAdjustHint(scanInfo.id)) { // postpone scan return; } else // hasMetadataAdjustedHint { { QWriteLocker locker(&d->hints->lock); d->hints->metadataAdjustedHints.remove(scanInfo.id); } scanFileUpdateHashReuseThumbnail(fi, scanInfo, true); return; } } else if (d->updatingHashHint) { // if the file need not be scanned because of modification, update the hash if (s_modificationDateEquals(fi.lastModified(), scanInfo.modificationDate) && fi.size() == scanInfo.fileSize) { scanFileUpdateHashReuseThumbnail(fi, scanInfo, false); return; } } MetaEngineSettingsContainer settings = MetaEngineSettings::instance()->settings(); QDateTime modificationDate = fi.lastModified(); if (checkSidecar && settings.updateFileTimeStamp && settings.useXMPSidecar4Reading && DMetadata::hasSidecar(fi.filePath())) { QString filePath = DMetadata::sidecarPath(fi.filePath()); QDateTime sidecarDate = QFileInfo(filePath).lastModified(); if (sidecarDate > modificationDate) { modificationDate = sidecarDate; } } if (!s_modificationDateEquals(modificationDate, scanInfo.modificationDate) || fi.size() != scanInfo.fileSize) { if (settings.rescanImageIfModified) { rescanFile(fi, scanInfo); } else { scanModifiedFile(fi, scanInfo); } } } qlonglong CollectionScanner::scanNewFile(const QFileInfo& info, int albumId) { if (d->checkDeferred(info)) { return -1; } ItemScanner scanner(info); scanner.setCategory(category(info)); // Check copy/move hints for single items qlonglong srcId = 0; if (d->hints) { QReadLocker locker(&d->hints->lock); srcId = d->hints->itemHints.value(NewlyAppearedFile(albumId, info.fileName())); } if (srcId != 0) { scanner.copiedFrom(albumId, srcId); } else { // Check copy/move hints for whole albums int srcAlbum = d->establishedSourceAlbums.value(albumId); if (srcAlbum) { // if we have one source album, find out if there is a file with the same name srcId = CoreDbAccess().db()->getImageId(srcAlbum, info.fileName()); } if (srcId != 0) { scanner.copiedFrom(albumId, srcId); } else { // Establishing identity with the unique hsah scanner.newFile(albumId); } } d->finishScanner(scanner); return scanner.id(); } qlonglong CollectionScanner::scanNewFileFullScan(const QFileInfo& info, int albumId) { if (d->checkDeferred(info)) { return -1; } ItemScanner scanner(info); scanner.setCategory(category(info)); scanner.newFileFullScan(albumId); d->finishScanner(scanner); return scanner.id(); } void CollectionScanner::scanModifiedFile(const QFileInfo& info, const ItemScanInfo& scanInfo) { if (d->checkDeferred(info)) { return; } ItemScanner scanner(info, scanInfo); scanner.setCategory(category(info)); scanner.fileModified(); d->finishScanner(scanner); } void CollectionScanner::scanFileUpdateHashReuseThumbnail(const QFileInfo& info, const ItemScanInfo& scanInfo, bool fileWasEdited) { QString oldHash = scanInfo.uniqueHash; qlonglong oldSize = scanInfo.fileSize; // same code as scanModifiedFile ItemScanner scanner(info, scanInfo); scanner.setCategory(category(info)); scanner.fileModified(); QString newHash = scanner.itemScanInfo().uniqueHash; qlonglong newSize = scanner.itemScanInfo().fileSize; if (ThumbsDbAccess::isInitialized()) { if (fileWasEdited) { // The file was edited in such a way that we know that the pixel content did not change, so we can reuse the thumbnail. // We need to add a link to the thumbnail data with the new hash/file size _and_ adjust // the file modification date in the data table. ThumbsDbInfo thumbDbInfo = ThumbsDbAccess().db()->findByHash(oldHash, oldSize); if (thumbDbInfo.id != -1) { ThumbsDbAccess().db()->insertUniqueHash(newHash, newSize, thumbDbInfo.id); ThumbsDbAccess().db()->updateModificationDate(thumbDbInfo.id, scanner.itemScanInfo().modificationDate); // TODO: also update details thumbnails (by file path and URL scheme) } } else { ThumbsDbAccess().db()->replaceUniqueHash(oldHash, oldSize, newHash, newSize); } } d->finishScanner(scanner); } void CollectionScanner::rescanFile(const QFileInfo& info, const ItemScanInfo& scanInfo) { if (d->checkDeferred(info)) { return; } ItemScanner scanner(info, scanInfo); scanner.setCategory(category(info)); scanner.rescan(); d->finishScanner(scanner); } void CollectionScanner::completeHistoryScanning() { // scan tagged images int needResolvingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needResolvingHistory()); int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); QList ids = CoreDbAccess().db()->getItemIDsInTag(needResolvingTag); historyScanningStage2(ids); ids = CoreDbAccess().db()->getItemIDsInTag(needTaggingTag); qCDebug(DIGIKAM_DATABASE_LOG) << "items to tag" << ids; historyScanningStage3(ids); } void CollectionScanner::finishHistoryScanning() { // scan recorded ids QList ids; // stage 2 ids = d->needResolveHistorySet.toList(); d->needResolveHistorySet.clear(); historyScanningStage2(ids); if (!d->checkObserver()) { return; } // stage 3 ids = d->needTaggingHistorySet.toList(); d->needTaggingHistorySet.clear(); historyScanningStage3(ids); } void CollectionScanner::historyScanningStage2(const QList& ids) { foreach (const qlonglong& id, ids) { if (!d->checkObserver()) { return; } CoreDbOperationGroup group; if (d->recordHistoryIds) { QList needTaggingIds; ItemScanner::resolveImageHistory(id, &needTaggingIds); foreach (const qlonglong& needTag, needTaggingIds) { d->needTaggingHistorySet << needTag; } } else { ItemScanner::resolveImageHistory(id); } } } void CollectionScanner::historyScanningStage3(const QList& ids) { foreach (const qlonglong& id, ids) { if (!d->checkObserver()) { return; } CoreDbOperationGroup group; ItemScanner::tagItemHistoryGraph(id); } } bool CollectionScanner::databaseInitialScanDone() { CoreDbAccess access; return !access.db()->getSetting(QLatin1String("Scanned")).isEmpty(); } } // namespace Digikam diff --git a/core/libs/database/collection/collectionscanner_utils.cpp b/core/libs/database/collection/collectionscanner_utils.cpp index 64a1f49968..e8a440f0fa 100644 --- a/core/libs/database/collection/collectionscanner_utils.cpp +++ b/core/libs/database/collection/collectionscanner_utils.cpp @@ -1,744 +1,744 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-03-21 * Description : Collection scanning to database - scan utilities. * * Copyright (C) 2005-2006 by Tom Albers * Copyright (C) 2007-2011 by Marcel Wiesweg * Copyright (C) 2009-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "collectionscanner_p.h" namespace Digikam { void CollectionScanner::loadNameFilters() { if (!d->nameFilters.isEmpty()) { return; } QStringList imageFilter, audioFilter, videoFilter, ignoreDirectory; CoreDbAccess().db()->getFilterSettings(&imageFilter, &videoFilter, &audioFilter); CoreDbAccess().db()->getIgnoreDirectoryFilterSettings(&ignoreDirectory); // three sets to find category of a file d->imageFilterSet = imageFilter.toSet(); d->audioFilterSet = audioFilter.toSet(); d->videoFilterSet = videoFilter.toSet(); d->ignoreDirectory = ignoreDirectory.toSet(); d->nameFilters = d->imageFilterSet + d->audioFilterSet + d->videoFilterSet; } void CollectionScanner::mainEntryPoint(bool complete) { loadNameFilters(); d->recordHistoryIds = !complete; } void CollectionScanner::safelyRemoveAlbums(const QList& albumIds) { // Remove the items (orphan items, detach them from the album, but keep entries for a certain time) // Make album orphan (no album root, keep entries until next application start) CoreDbAccess access; CoreDbTransaction transaction(&access); foreach (int albumId, albumIds) { QList ids = access.db()->getItemIDsInAlbum(albumId); access.db()->removeItemsFromAlbum(albumId, ids); access.db()->makeStaleAlbum(albumId); itemsWereRemoved(ids); } } int CollectionScanner::checkAlbum(const CollectionLocation& location, const QString& album) { // get album id if album exists int albumID = CoreDbAccess().db()->getAlbumForPath(location.id(), album, false); d->establishedSourceAlbums.remove(albumID); // create if necessary if (albumID == -1) { QFileInfo fi(location.albumRootPath() + album); albumID = CoreDbAccess().db()->addAlbum(location.id(), album, QString(), fi.lastModified().date(), QString()); // have album this one was copied from? if (d->hints) { CollectionScannerHints::Album src; { QReadLocker locker(&d->hints->lock); src = d->hints->albumHints.value(CollectionScannerHints::DstPath(location.id(), album)); } if (!src.isNull()) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Identified album" << src.albumId << "as source of new album" << fi.filePath(); CoreDbAccess().db()->copyAlbumProperties(src.albumId, albumID); d->establishedSourceAlbums[albumID] = src.albumId; } } } return albumID; } void CollectionScanner::copyFileProperties(const ItemInfo& source, const ItemInfo& d) { if (source.isNull() || d.isNull()) { return; } ItemInfo dest(d); CoreDbOperationGroup group; qCDebug(DIGIKAM_DATABASE_LOG) << "Copying properties from" << source.id() << "to" << dest.id(); // Rating, creation dates DatabaseFields::ItemInformation imageInfoFields = DatabaseFields::Rating | DatabaseFields::CreationDate | DatabaseFields::DigitizationDate; QVariantList imageInfos = CoreDbAccess().db()->getItemInformation(source.id(), imageInfoFields); if (!imageInfos.isEmpty()) { CoreDbAccess().db()->changeItemInformation(dest.id(), imageInfos, imageInfoFields); } // Copy public tags foreach (int tagId, TagsCache::instance()->publicTags(source.tagIds())) { dest.setTag(tagId); } // Copy color and pick label dest.setPickLabel(source.pickLabel()); dest.setColorLabel(source.colorLabel()); // important: skip other internal tags, such a history tags. Therefore CoreDB::copyImageTags is not to be used. // GPS data QVariantList positionData = CoreDbAccess().db()->getItemPosition(source.id(), DatabaseFields::ItemPositionsAll); if (!positionData.isEmpty()) { CoreDbAccess().db()->addItemPosition(dest.id(), positionData, DatabaseFields::ItemPositionsAll); } // Comments { CoreDbAccess access; ItemComments commentsSource(access, source.id()); ItemComments commentsDest(access, dest.id()); commentsDest.replaceFrom(commentsSource); commentsDest.apply(access); } // Copyright info ItemCopyright copyrightDest(dest.id()); copyrightDest.replaceFrom(ItemCopyright(source.id())); // Image Properties CoreDbAccess().db()->copyImageProperties(source.id(), dest.id()); } void CollectionScanner::itemsWereRemoved(const QList& removedIds) { // set time stamp d->removedItems(); // manage relations QList relatedImages = CoreDbAccess().db()->getOneRelatedImageEach(removedIds, DatabaseRelation::DerivedFrom); qCDebug(DIGIKAM_DATABASE_LOG) << "Removed items:" << removedIds << "related items:" << relatedImages; if (d->recordHistoryIds) { foreach (const qlonglong& id, relatedImages) { d->needTaggingHistorySet << id; } } else { int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph()); CoreDbAccess().db()->addTagsToItems(relatedImages, QList() << needTaggingTag); } } int CollectionScanner::countItemsInFolder(const QString& directory) { int items = 0; QDir dir(directory); if (!dir.exists() || !dir.isReadable()) { return 0; } QDirIterator it(dir.path(), QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); ++items; } return items; } DatabaseItem::Category CollectionScanner::category(const QFileInfo& info) { QString suffix = info.suffix().toLower(); if (d->imageFilterSet.contains(suffix)) { return DatabaseItem::Image; } else if (d->audioFilterSet.contains(suffix)) { return DatabaseItem::Audio; } else if (d->videoFilterSet.contains(suffix)) { return DatabaseItem::Video; } else { return DatabaseItem::Other; } } void CollectionScanner::markDatabaseAsScanned() { CoreDbAccess access; access.db()->setSetting(QLatin1String("Scanned"), QDateTime::currentDateTime().toString(Qt::ISODate)); } void CollectionScanner::updateRemovedItemsTime() { // Called after a complete or partial scan finishes, to write the value // held in d->removedItemsTime to the database if (!d->removedItemsTime.isNull()) { CoreDbAccess().db()->setSetting(QLatin1String("RemovedItemsTime"), d->removedItemsTime.toString(Qt::ISODate)); d->removedItemsTime = QDateTime(); } } void CollectionScanner::incrementDeleteRemovedCompleteScanCount() { CoreDbAccess access; int count = access.db()->getSetting(QLatin1String("DeleteRemovedCompleteScanCount")).toInt(); ++count; access.db()->setSetting(QLatin1String("DeleteRemovedCompleteScanCount"), QString::number(count)); } void CollectionScanner::resetDeleteRemovedSettings() { CoreDbAccess().db()->setSetting(QLatin1String("RemovedItemsTime"), QString()); CoreDbAccess().db()->setSetting(QLatin1String("DeleteRemovedTime"), QDateTime::currentDateTime().toString(Qt::ISODate)); CoreDbAccess().db()->setSetting(QLatin1String("DeleteRemovedCompleteScanCount"), QString::number(0)); } bool CollectionScanner::checkDeleteRemoved() { // returns true if removed items shall be deleted CoreDbAccess access; // retrieve last time an item was removed (not deleted, but set to status removed) QString removedItemsTimeString = access.db()->getSetting(QLatin1String("RemovedItemsTime")); if (removedItemsTimeString.isNull()) { return false; } // retrieve last time removed items were (definitely) deleted from db QString deleteRemovedTimeString = access.db()->getSetting(QLatin1String("DeleteRemovedTime")); QDateTime removedItemsTime, deleteRemovedTime; if (!removedItemsTimeString.isNull()) { removedItemsTime = QDateTime::fromString(removedItemsTimeString, Qt::ISODate); } if (!deleteRemovedTimeString.isNull()) { deleteRemovedTime = QDateTime::fromString(deleteRemovedTimeString, Qt::ISODate); } QDateTime now = QDateTime::currentDateTime(); // retrieve number of complete collection scans since the last time that removed items were deleted int completeScans = access.db()->getSetting(QLatin1String("DeleteRemovedCompleteScanCount")).toInt(); // No removed items? No need to delete any if (!removedItemsTime.isValid()) { return false; } // give at least a week between removed item deletions if (deleteRemovedTime.isValid()) { if (deleteRemovedTime.daysTo(now) <= 7) { return false; } } // Now look at time since items were removed, and the number of complete scans // since removed items were deleted. Values arbitrarily chosen. int daysPast = removedItemsTime.daysTo(now); return (daysPast > 7 && completeScans > 2) || (daysPast > 30 && completeScans > 0) || (completeScans > 30); } // ------------------------------------------------------------------------------------------ #if 0 void CollectionScanner::scanForStaleAlbums() { QStringList albumRootPaths = CollectionManager::instance()->allAvailableAlbumRootPaths(); for (QStringList::const_iterator it = albumRootPaths.constBegin(); it != albumRootPaths.constEnd(); ++it) { scanForStaleAlbums(*it); } } void CollectionScanner::scanForStaleAlbums(const QString& albumRoot) { Q_UNUSED(albumRoot); QList albumList = CoreDbAccess().db()->getAlbumShortInfos(); QList toBeDeleted; QList::const_iterator it; for (it = albumList.constBegin(); it != albumList.constEnd(); ++it) { QFileInfo fileInfo((*it).albumRoot + (*it).url); if (!fileInfo.exists() || !fileInfo.isDir()) { m_foldersToBeDeleted << (*it); } } } QStringList CollectionScanner::formattedListOfStaleAlbums() { QStringList list; QList::const_iterator it; for (it = m_foldersToBeDeleted.constBegin(); it != m_foldersToBeDeleted.constEnd(); ++it) { list << (*it).url; } return list; } void CollectionScanner::removeStaleAlbums() { CoreDbAccess access; CoreDbTransaction transaction(&access); QList::const_iterator it; for (it = m_foldersToBeDeleted.constBegin(); it != m_foldersToBeDeleted.constEnd(); ++it) { qCDebug(DIGIKAM_DATABASE_LOG) << "Removing album " << (*it).albumRoot + QLatin1Char('/') + (*it).url; access.db()->deleteAlbum((*it).id); } } QStringList CollectionScanner::formattedListOfStaleFiles() { QStringList listToBeDeleted; CoreDbAccess access; QList >::const_iterator it; for (it = m_filesToBeDeleted.constBegin(); it != m_filesToBeDeleted.constEnd(); ++it) { QString location = QLatin1String(" (") + access.db()->getAlbumPath((*it).second) + QLatin1Char(')'); listToBeDeleted.append((*it).first + location); } return listToBeDeleted; } void CollectionScanner::removeStaleFiles() { CoreDbAccess access; CoreDbTransaction transaction(&access); QList >::const_iterator it; for (it = m_filesToBeDeleted.constBegin(); it != m_filesToBeDeleted.constEnd(); ++it) { qCDebug(DIGIKAM_DATABASE_LOG) << "Removing: " << (*it).first << " in " << (*it).second; access.db()->deleteItem( (*it).second, (*it).first ); } } void CollectionScanner::scanAlbums() { QStringList albumRootPaths = CollectionManager::instance()->allAvailableAlbumRootPaths(); int count = 0; for (QStringList::const_iterator it = albumRootPaths.constBegin(); it != albumRootPaths.constEnd(); ++it) { count += countItemsInFolder(*it); } emit totalFilesToScan(count); for (QStringList::const_iterator it = albumRootPaths.constBegin(); it != albumRootPaths.constEnd(); ++it) { QDir dir(*it); QStringList fileList(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)); CoreDbTransaction transaction; - foreach(const QString& dir, fileList) + foreach (const QString& dir, fileList) { scanAlbum(*it, QLatin1Char('/') + dir); } } } void CollectionScanner::scan(const QString& folderPath) { CollectionManager* const manager = CollectionManager::instance(); QUrl url; url.setPath(folderPath); QString albumRoot = manager->albumRootPath(url); QString album = manager->album(url); if (albumRoot.isNull()) { qCWarning(DIGIKAM_DATABASE_LOG) << "scanAlbums(QString): folder " << folderPath << " not found in collection."; return; } scan(albumRoot, album); } void CollectionScanner::scan(const QString& albumRoot, const QString& album) { // Step one: remove invalid albums scanForStaleAlbums(albumRoot); removeStaleAlbums(); emit totalFilesToScan(countItemsInFolder(albumRoot + album)); // Step two: Scan directories if (album == QLatin1String("/")) { // Don't scan files under album root, only descend into directories (?) QDir dir(albumRoot + album); QStringList fileList(dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)); CoreDbTransaction transaction; for (QStringList::const_iterator fileIt = fileList.constBegin(); fileIt != fileList.constEnd(); ++fileIt) { scanAlbum(albumRoot, QLatin1Char('/') + (*fileIt)); } } else { CoreDbTransaction transaction; scanAlbum(albumRoot, album); } // Step three: Remove invalid files removeStaleFiles(); } void CollectionScanner::scanAlbum(const QString& filePath) { QUrl url; url.setPath(filePath); scanAlbum(CollectionManager::instance()->albumRootPath(url), CollectionManager::instance()->album(url)); } void CollectionScanner::scanAlbum(const QString& albumRoot, const QString& album) { // + Adds album if it does not yet exist in the db. // + Recursively scans subalbums of album. // + Adds files if they do not yet exist in the db. // + Adds stale files from the db to m_filesToBeDeleted // - Does not add stale albums to m_foldersToBeDeleted. QDir dir( albumRoot + album ); if ( !dir.exists() || !dir.isReadable() ) { qCWarning(DIGIKAM_DATABASE_LOG) << "Folder does not exist or is not readable: " << dir.path(); return; } emit startScanningAlbum(albumRoot, album); // get album id if album exists int albumID = CoreDbAccess().db()->getAlbumForPath(albumRoot, album, false); if (albumID == -1) { QFileInfo fi(albumRoot + album); albumID = CoreDbAccess().db()->addAlbum(albumRoot, album, QString(), fi.lastModified().date(), QString()); } QStringList filesInAlbum = CoreDbAccess().db()->getItemNamesInAlbum( albumID ); QSet filesFoundInDB; for (QStringList::const_iterator it = filesInAlbum.constBegin(); it != filesInAlbum.constEnd(); ++it) { filesFoundInDB << *it; } const QFileInfoList list = dir.entryInfoList(m_nameFilters, QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot /*not CaseSensitive*/); QFileInfoList::const_iterator fi; for (fi = list.constBegin(); fi != list.constEnd(); ++fi) { if ( fi->isFile()) { if (filesFoundInDB.contains(fi->fileName()) ) { filesFoundInDB.remove(fi->fileName()); } // ignore temp files we created ourselves else if (fi->completeSuffix() == QLatin1String("digikamtempfile.tmp")) { continue; } else { qCDebug(DIGIKAM_DATABASE_LOG) << "Adding item " << fi->fileName(); addItem(albumID, albumRoot, album, fi->fileName()); } } else if ( fi->isDir() ) { scanAlbum( albumRoot, album + QLatin1Char('/') + fi->fileName() ); } } // Removing items from the db which we did not see on disk. if (!filesFoundInDB.isEmpty()) { QSetIterator it(filesFoundInDB); while (it.hasNext()) { QPair pair(it.next(),albumID); if (m_filesToBeDeleted.indexOf(pair) == -1) { m_filesToBeDeleted << pair; } } } emit finishedScanningAlbum(albumRoot, album, list.count()); } void CollectionScanner::updateItemsWithoutDate() { QStringList urls = CoreDbAccess().db()->getAllItemURLsWithoutDate(); emit totalFilesToScan(urls.count()); QString albumRoot = CoreDbAccess::albumRoot(); { CoreDbTransaction transaction; for (QStringList::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it) { emit scanningFile(*it); QFileInfo fi(*it); QString albumURL = fi.path(); albumURL = QDir::cleanPath(albumURL.remove(albumRoot)); int albumID = CoreDbAccess().db()->getAlbumForPath(albumRoot, albumURL); if (albumID <= 0) { qCWarning(DIGIKAM_DATABASE_LOG) << "Album ID == -1: " << albumURL; } if (fi.exists()) { CollectionScanner::updateItemDate(albumID, albumRoot, albumURL, fi.fileName()); } else { QPair pair(fi.fileName(), albumID); if (m_filesToBeDeleted.indexOf(pair) == -1) { m_filesToBeDeleted << pair; } } } } } int CollectionScanner::countItemsInFolder(const QString& directory) { int items = 0; QDir dir( directory ); if ( !dir.exists() || !dir.isReadable() ) { return 0; } QFileInfoList list = dir.entryInfoList(); items += list.count(); QFileInfoList::const_iterator fi; for (fi = list.constBegin(); fi != list.constEnd(); ++fi) { if ( fi->isDir() && fi->fileName() != QLatin1String(".") && fi->fileName() != QLatin1String("..")) { items += countItemsInFolder( fi->filePath() ); } } return items; } void CollectionScanner::markDatabaseAsScanned() { CoreDbAccess access; access.db()->setSetting("Scanned", QDateTime::currentDateTime().toString(Qt::ISODate)); } // ------------------- Tools ------------------------ void CollectionScanner::addItem(int albumID, const QString& albumRoot, const QString& album, const QString& fileName) { CoreDbAccess access; addItem(access, albumID, albumRoot, album, fileName); } void CollectionScanner::addItem(Digikam::CoreDbAccess& access, int albumID, const QString& albumRoot, const QString& album, const QString& fileName) { QString filePath = albumRoot + album + QLatin1Char('/') + fileName; QString comment; QStringList keywords; QDateTime datetime; int rating; DMetadata metadata(filePath); // Try to get comments from image : // In first, from standard JPEG comments, or // In second, from EXIF comments tag, or // In third, from IPTC comments tag. comment = metadata.getImageComment(); // Try to get date and time from image : // In first, from EXIF date & time tags, or // In second, from IPTC date & time tags. datetime = metadata.getItemDateTime(); // Try to get image rating from IPTC Urgency tag // else use file system time stamp. rating = metadata.getItemRating(); if ( !datetime.isValid() ) { QFileInfo info( filePath ); datetime = info.lastModified(); } // Try to get image tags from IPTC keywords tags. metadata.getItemTagsPath(keywords); access.db()->addItem(albumID, fileName, datetime, comment, rating, keywords); } void CollectionScanner::updateItemDate(int albumID, const QString& albumRoot, const QString& album, const QString& fileName) { CoreDbAccess access; updateItemDate(access, albumID, albumRoot, album, fileName); } void CollectionScanner::updateItemDate(Digikam::CoreDbAccess& access, int albumID, const QString& albumRoot, const QString& album, const QString& fileName) { QString filePath = albumRoot + album + QLatin1Char('/') + fileName; QDateTime datetime; DMetadata metadata(filePath); // Trying to get date and time from image : // In first, from EXIF date & time tags, or // In second, from IPTC date & time tags. datetime = metadata.getItemDateTime(); if ( !datetime.isValid() ) { QFileInfo info( filePath ); datetime = info.lastModified(); } access.db()->setItemDate(albumID, fileName, datetime); } #endif } // namespace Digikam diff --git a/core/libs/database/coredb/coredbbackend_p.h b/core/libs/database/coredb/coredbbackend_p.h index 914a321efb..e01816f333 100644 --- a/core/libs/database/coredb/coredbbackend_p.h +++ b/core/libs/database/coredb/coredbbackend_p.h @@ -1,155 +1,155 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-06-07 * Description : Core database abstract backend. * * Copyright (C) 2007-2009 by Marcel Wiesweg * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef DIGIKAM_CORE_DB_BACKEND_PRIVATE_H #define DIGIKAM_CORE_DB_BACKEND_PRIVATE_H // Local includes #include "dbenginebackend_p.h" #include "coredbwatch.h" namespace Digikam { class CoreDbWatch; class Q_DECL_HIDDEN CoreDbBackendPrivate : public BdEngineBackendPrivate { public: explicit CoreDbBackendPrivate(CoreDbBackend* const backend) : BdEngineBackendPrivate(backend), imageChangesetContainer(this), imageTagChangesetContainer(this), collectionImageChangesetContainer(this), albumChangesetContainer(this), tagChangesetContainer(this), albumRootChangesetContainer(this), searchChangesetContainer(this) { watch = nullptr; } public: CoreDbWatch* watch; public: void sendToWatch(const ImageChangeset changeset) { watch->sendImageChange(changeset); } void sendToWatch(const ImageTagChangeset changeset) { watch->sendImageTagChange(changeset); } void sendToWatch(const CollectionImageChangeset changeset) { watch->sendCollectionImageChange(changeset); } void sendToWatch(const AlbumChangeset changeset) { watch->sendAlbumChange(changeset); } void sendToWatch(const TagChangeset changeset) { watch->sendTagChange(changeset); } void sendToWatch(const AlbumRootChangeset changeset) { watch->sendAlbumRootChange(changeset); } void sendToWatch(const SearchChangeset changeset) { watch->sendSearchChange(changeset); } template class Q_DECL_HIDDEN ChangesetContainer { public: explicit ChangesetContainer(CoreDbBackendPrivate* const d) : d(d) { } void recordChangeset(const T& changeset) { if (d->isInTransaction) { changesets << changeset; } else { d->sendToWatch(changeset); } } void sendOut() { - foreach(const T& changeset, changesets) + foreach (const T& changeset, changesets) { d->sendToWatch(changeset); } changesets.clear(); } public: QList changesets; CoreDbBackendPrivate* const d; }; public: ChangesetContainer imageChangesetContainer; ChangesetContainer imageTagChangesetContainer; ChangesetContainer collectionImageChangesetContainer; ChangesetContainer albumChangesetContainer; ChangesetContainer tagChangesetContainer; ChangesetContainer albumRootChangesetContainer; ChangesetContainer searchChangesetContainer; public: void transactionFinished() override { BdEngineBackendPrivate::transactionFinished(); imageChangesetContainer.sendOut(); imageTagChangesetContainer.sendOut(); collectionImageChangesetContainer.sendOut(); albumChangesetContainer.sendOut(); tagChangesetContainer.sendOut(); albumRootChangesetContainer.sendOut(); searchChangesetContainer.sendOut(); } }; } // namespace Digikam #endif // DIGIKAM_CORE_DB_BACKEND_PRIVATE_H diff --git a/core/libs/database/coredb/coredbcopymanager.cpp b/core/libs/database/coredb/coredbcopymanager.cpp index 546ede1e59..c5b40f2ae3 100644 --- a/core/libs/database/coredb/coredbcopymanager.cpp +++ b/core/libs/database/coredb/coredbcopymanager.cpp @@ -1,304 +1,304 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-11-14 * Description : Core database copy manager for migration operations. * * Copyright (C) 2009-2010 by Holger Foerster * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "coredbcopymanager.h" // Qt includes #include #include #include // KDE Includes #include // Local includes #include "digikam_debug.h" #include "dbenginebackend.h" #include "dbengineparameters.h" #include "coredb.h" #include "coredbschemaupdater.h" namespace Digikam { CoreDbCopyManager::CoreDbCopyManager() { m_isStopProcessing = false; } CoreDbCopyManager::~CoreDbCopyManager() { } void CoreDbCopyManager::stopProcessing() { m_isStopProcessing = true; } void CoreDbCopyManager::copyDatabases(const DbEngineParameters& fromDBParameters, DbEngineParameters& toDBParameters) { m_isStopProcessing = false; DbEngineLocking fromLocking; CoreDbBackend fromDBbackend(&fromLocking, QLatin1String("MigrationFromDatabase")); if (!fromDBbackend.open(fromDBParameters)) { emit finished(CoreDbCopyManager::failed, i18n("Error while opening the source database.")); return; } DbEngineLocking toLocking; CoreDbBackend toDBbackend(&toLocking, QLatin1String("MigrationToDatabase")); if (!toDBbackend.open(toDBParameters)) { emit finished(CoreDbCopyManager::failed, i18n("Error while opening the target database.")); fromDBbackend.close(); return; } // Order may be important, array class must _not_ be sorted const QStringList tables = QStringList() << QLatin1String("AlbumRoots") << QLatin1String("Albums") << QLatin1String("Images") // Virtual table used to allow population of Albums.icon after Images migration << QLatin1String("AlbumsExtra") << QLatin1String("ImageInformation") << QLatin1String("ImageMetadata") << QLatin1String("ImagePositions") << QLatin1String("ImageComments") << QLatin1String("ImageCopyright") << QLatin1String("Tags") << QLatin1String("TagProperties") << QLatin1String("ImageTagProperties") << QLatin1String("ImageTags") << QLatin1String("ImageProperties") << QLatin1String("ImageHistory") << QLatin1String("ImageRelations") << QLatin1String("Searches") << QLatin1String("DownloadHistory") << QLatin1String("VideoMetadata") << QLatin1String("Settings") ; const int tablesSize = tables.size(); QMap bindingMap; // Run any database specific preparatory cleanup prior to dropping tables. DbEngineAction action = toDBbackend.getDBAction(QString::fromUtf8("Migrate_Cleanup_Prepare")); BdEngineBackend::QueryState queryStateResult = toDBbackend.execDBAction(action); // Accept SQL error because the foreign key may not exist. if (queryStateResult == BdEngineBackend::ConnectionError) { emit finished(CoreDbCopyManager::failed, i18n("Error while preparing the target database.")); } // Delete all tables for (int i = (tablesSize - 1); m_isStopProcessing || i >= 0; --i) { if ( m_isStopProcessing || toDBbackend.execDirectSql(QString::fromUtf8("DROP TABLE IF EXISTS %1;").arg(tables[i])) != BdEngineBackend::NoErrors) { emit finished(CoreDbCopyManager::failed, i18n("Error while scrubbing the target database.")); fromDBbackend.close(); toDBbackend.close(); return; } } // Then create the schema CoreDB albumDB(&toDBbackend); CoreDbSchemaUpdater updater(&albumDB, &toDBbackend, toDBParameters); emit stepStarted(i18n("Create Schema...")); if (!updater.update()) { emit finished(CoreDbCopyManager::failed, i18n("Error while creating the database schema.")); fromDBbackend.close(); toDBbackend.close(); return; } // loop copying the tables, stop if an error is met for (int i = 0; m_isStopProcessing || i < tablesSize; ++i) { if (i < tablesSize) { emit stepStarted(i18n(QString::fromUtf8("Copy %1...").arg(tables[i]).toLatin1().constData())); } // Now perform the copy action if ( m_isStopProcessing || !copyTable(fromDBbackend, QString::fromUtf8("Migrate_Read_%1").arg(tables[i]), toDBbackend, QString::fromUtf8("Migrate_Write_%1").arg(tables[i])) ) { handleClosing(m_isStopProcessing, fromDBbackend, toDBbackend); return; } } fromDBbackend.close(); toDBbackend.close(); emit smallStepStarted(1, 1); emit finished(CoreDbCopyManager::success, QString()); } bool CoreDbCopyManager::copyTable(CoreDbBackend& fromDBbackend, const QString& fromActionName, CoreDbBackend& toDBbackend, const QString& toActionName) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: trying to copy contents from DB with ActionName: [" << fromActionName << "] to DB with ActionName [" << toActionName << "]"; QMap bindingMap; // now perform the copy action QList columnNames; QSqlQuery result = fromDBbackend.execDBActionQuery(fromDBbackend.getDBAction(fromActionName), bindingMap) ; int resultSize = -1; bool isForwardOnly = result.isForwardOnly(); if (result.driver()->hasFeature(QSqlDriver::QuerySize)) { resultSize=result.size(); } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: driver do not support query size. " "We try to go to the last row and back to the current."; result.last(); // Now get the current row. If this is not possible, a value lower than 0 will be returned. // To not confuse the log reading user, we reset this value to 0. resultSize = (result.at() < 0) ? 0 : result.at(); // Avoid a misleading error message, query is redone if isForwardOnly if ( ! isForwardOnly) { result.first(); } } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: result size: ["<< resultSize << "]"; // If the sql query is forward only - perform the query again. // This is not atomic, so it can be tend to different results between // real database entries copied and shown at the progressbar. if (isForwardOnly) { result.finish(); result = fromDBbackend.execDBActionQuery(fromDBbackend.getDBAction(fromActionName), bindingMap) ; } int columnCount = result.record().count(); for (int i = 0; i < columnCount; ++i) { //qCDebug(DIGIKAM_COREDB_LOG) << "Column: ["<< result.record().fieldName(i) << "]"; columnNames.append(result.record().fieldName(i)); } int resultCounter = 0; while (result.next()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: query isOnValidRow [" << result.isValid() << "] isActive [" << result.isActive() << "] result size: [" << result.size() << "]"; if (m_isStopProcessing == true) { return false; } // Send a signal to the GUI to entertain the user emit smallStepStarted(resultCounter++, resultSize); // Read the values from the fromDB into a hash QMap tempBindingMap; int i = 0; - foreach(QString columnName, columnNames) // krazy:exclude=foreach + foreach (QString columnName, columnNames) // krazy:exclude=foreach { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: column: [" << columnName << "] value [" << result.value(i) << "]"; tempBindingMap.insert(columnName.insert(0, QLatin1Char(':')), result.value(i)); ++i; } // Insert the previous requested values to the DB DbEngineAction action = toDBbackend.getDBAction(toActionName); BdEngineBackend::QueryState queryStateResult = toDBbackend.execDBAction(action, tempBindingMap); if (queryStateResult != BdEngineBackend::NoErrors && toDBbackend.lastSQLError().isValid() && !toDBbackend.lastSQLError().nativeErrorCode().isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: error while converting table data. Details: " << toDBbackend.lastSQLError(); QString errorMsg = i18n("Error while converting the database.\n Details: %1", toDBbackend.lastSQLError().databaseText()); emit finished(CoreDbCopyManager::failed, errorMsg); return false; } } return true; } void CoreDbCopyManager::handleClosing(bool isStopThread, CoreDbBackend& fromDBbackend, CoreDbBackend& toDBbackend) { if (isStopThread) { emit finished(CoreDbCopyManager::canceled, QLatin1String("")); } fromDBbackend.close(); toDBbackend.close(); } } // namespace Digikam diff --git a/core/libs/database/coredb/coredbdownloadhistory.cpp b/core/libs/database/coredb/coredbdownloadhistory.cpp index 12a26babec..6f54ae4d4b 100644 --- a/core/libs/database/coredb/coredbdownloadhistory.cpp +++ b/core/libs/database/coredb/coredbdownloadhistory.cpp @@ -1,60 +1,60 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-11-01 * Description : Core database interface to manage camera item download history. * * Copyright (C) 2007-2008 by Marcel Wiesweg * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "coredbdownloadhistory.h" // Local includes #include "coredb.h" #include "coredbaccess.h" namespace Digikam { CamItemInfo::DownloadStatus CoreDbDownloadHistory::status(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date) { QList seconds; seconds << 0 << 3600 << -3600; - foreach(const qint64 secound, seconds) + foreach (const qint64 secound, seconds) { QDateTime dt = date.addSecs(secound); if (CoreDbAccess().db()->findInDownloadHistory(identifier, name, fileSize, dt) != -1) { return CamItemInfo::DownloadedYes; } } return CamItemInfo::DownloadedNo; } void CoreDbDownloadHistory::setDownloaded(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date) { CoreDbAccess().db()->addToDownloadHistory(identifier, name, fileSize, date); } } // namespace Digikam diff --git a/core/libs/database/coredb/coredbschemaupdater.cpp b/core/libs/database/coredb/coredbschemaupdater.cpp index cf9be3cf50..82c59fa7cc 100644 --- a/core/libs/database/coredb/coredbschemaupdater.cpp +++ b/core/libs/database/coredb/coredbschemaupdater.cpp @@ -1,1594 +1,1594 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-16 * Description : Core database Schema updater * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2009-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "coredbschemaupdater.h" // Qt includes #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "coredbbackend.h" #include "coredbtransaction.h" #include "coredbchecker.h" #include "collectionmanager.h" #include "collectionlocation.h" #include "collectionscanner.h" #include "itemquerybuilder.h" #include "collectionscannerobserver.h" #include "digikam_config.h" namespace Digikam { int CoreDbSchemaUpdater::schemaVersion() { return 10; } int CoreDbSchemaUpdater::filterSettingsVersion() { return 9; } int CoreDbSchemaUpdater::uniqueHashVersion() { return 2; } bool CoreDbSchemaUpdater::isUniqueHashUpToDate() { return CoreDbAccess().db()->getUniqueHashVersion() >= uniqueHashVersion(); } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN CoreDbSchemaUpdater::Private { public: explicit Private() : setError(false), backend(nullptr), albumDB(nullptr), dbAccess(nullptr), observer(nullptr) { } bool setError; QVariant currentVersion; QVariant currentRequiredVersion; CoreDbBackend* backend; CoreDB* albumDB; DbEngineParameters parameters; // legacy CoreDbAccess* dbAccess; QString lastErrorMessage; InitializationObserver* observer; }; CoreDbSchemaUpdater::CoreDbSchemaUpdater(CoreDB* const albumDB, CoreDbBackend* const backend, DbEngineParameters parameters) : d(new Private) { d->backend = backend; d->albumDB = albumDB; d->parameters = parameters; } CoreDbSchemaUpdater::~CoreDbSchemaUpdater() { delete d; } void CoreDbSchemaUpdater::setCoreDbAccess(CoreDbAccess* const dbAccess) { d->dbAccess = dbAccess; } const QString CoreDbSchemaUpdater::getLastErrorMessage() { return d->lastErrorMessage; } bool CoreDbSchemaUpdater::update() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: running schema update"; bool success = startUpdates(); // cancelled? if (d->observer && !d->observer->continueQuery()) { return false; } // even on failure, try to set current version - it may have incremented setVersionSettings(); if (!success) { return false; } updateFilterSettings(); if (d->observer) { d->observer->finishedSchemaUpdate(InitializationObserver::UpdateSuccess); } return success; } void CoreDbSchemaUpdater::setVersionSettings() { if (d->currentVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersion"), QString::number(d->currentVersion.toInt())); } if (d->currentRequiredVersion.isValid()) { d->albumDB->setSetting(QLatin1String("DBVersionRequired"), QString::number(d->currentRequiredVersion.toInt())); } } static QVariant safeToVariant(const QString& s) { if (s.isEmpty()) { return QVariant(); } else { return s.toInt(); } } void CoreDbSchemaUpdater::readVersionSettings() { d->currentVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersion"))); d->currentRequiredVersion = safeToVariant(d->albumDB->getSetting(QLatin1String("DBVersionRequired"))); } void CoreDbSchemaUpdater::setObserver(InitializationObserver* const observer) { d->observer = observer; } bool CoreDbSchemaUpdater::startUpdates() { if (!d->parameters.isSQLite()) { // Do we have sufficient privileges QStringList insufficientRights; CoreDbPrivilegesChecker checker(d->parameters); if (!checker.checkPrivileges(insufficientRights)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: insufficient rights on database."; QString errorMsg = i18n("You have insufficient privileges on the database.\n" "Following privileges are not assigned to you:\n %1\n" "Check your privileges on the database and restart digiKam.", insufficientRights.join(QLatin1String(",\n"))); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } // First step: do we have an empty database? QStringList tables = d->backend->tables(); if (tables.contains(QLatin1String("Albums"), Qt::CaseInsensitive)) { // Find out schema version of db file readVersionSettings(); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: have a structure version " << d->currentVersion.toInt(); // We absolutely require the DBVersion setting if (!d->currentVersion.isValid()) { // Something is damaged. Give up. qCDebug(DIGIKAM_COREDB_LOG) << "Core database: version not available! Giving up schema upgrading."; QString errorMsg = i18n("The database is not valid: " "the \"DBVersion\" setting does not exist. " "The current database schema version cannot be verified. " "Try to start with an empty database. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } // current version describes the current state of the schema in the db, // schemaVersion is the version required by the program. if (d->currentVersion.toInt() > schemaVersion()) { // trying to open a database with a more advanced than this CoreDbSchemaUpdater supports if (d->currentRequiredVersion.isValid() && d->currentRequiredVersion.toInt() <= schemaVersion()) { // version required may be less than current version return true; } else { QString errorMsg = i18n("The database has been used with a more recent version of digiKam " "and has been updated to a database schema which cannot be used with this version. " "(This means this digiKam version is too old, or the database format is too recent.) " "Please use the more recent version of digiKam that you used before. "); d->lastErrorMessage=errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } } else { return makeUpdates(); } } else { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: no database file available"; // Legacy handling? // first test if there are older files that need to be upgraded. // This applies to "digikam.db" for 0.7 and "digikam3.db" for 0.8 and 0.9, // all only SQLite databases. // Version numbers used in this source file are a bit confused for the historic versions. // Version 1 is 0.6 (no db), Version 2 is 0.7 (SQLite 2), // Version 3 is 0.8-0.9, // Version 3 wrote the setting "DBVersion", "1", // Version 4 is 0.10, the digikam3.db file copied to digikam4.db, // no schema changes. // Version 4 writes "4", and from now on version x writes "x". // Version 5 includes the schema changes from 0.9 to 0.10 // Version 6 brought new tables for history and ImageTagProperties, with version 2.0 // Version 7 brought the VideoMetadata table with 3.0 if (d->parameters.isSQLite()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QFileInfo digikam3DB(currentDBFile.dir(), QLatin1String("digikam3.db")); if (digikam3DB.exists()) { if (!copyV3toV4(digikam3DB.filePath(), currentDBFile.filePath())) { return false; } // d->currentVersion is now 4; return makeUpdates(); } } // No legacy handling: start with a fresh db if (!createDatabase() || !createFilterSettings()) { QString errorMsg = i18n("Failed to create tables in database.\n ") + d->backend->lastError(); d->lastErrorMessage = errorMsg; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } return true; } } bool CoreDbSchemaUpdater::beginWrapSchemaUpdateStep() { if (!d->backend->beginTransaction()) { QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("Failed to open a database transaction on your database file \"%1\". " "This is unusual. Please check that you can access the file and no " "other process has currently locked the file. " "If the problem persists you can get help from the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QDir::toNativeSeparators(currentDBFile.filePath())); d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); return false; } return true; } bool CoreDbSchemaUpdater::endWrapSchemaUpdateStep(bool stepOperationSuccess, const QString& errorMsg) { if (!stepOperationSuccess) { d->backend->rollbackTransaction(); if (d->observer) { // error or cancelled? if (!d->observer->continueQuery()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update cancelled by user"; } else if (!d->setError) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } } return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); d->backend->commitTransaction(); return true; } bool CoreDbSchemaUpdater::makeUpdates() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: makeUpdates " << d->currentVersion.toInt() << " to " << schemaVersion(); if (d->currentVersion.toInt() < schemaVersion()) { if (d->currentVersion.toInt() < 5) { if (!beginWrapSchemaUpdateStep()) { return false; } // v4 was always SQLite QFileInfo currentDBFile(d->parameters.databaseNameCore); QString errorMsg = i18n("The schema updating process from version 4 to 6 failed, " "caused by an error that we did not expect. " "You can try to discard your old database and start with an empty one. " "(In this case, please move the database files " "\"%1\" and \"%2\" from the directory \"%3\"). " "More probably you will want to report this error to the digikam developers mailing list (see www.digikam.org/support). " "As well, please have a look at what digiKam prints on the console. ", QLatin1String("digikam3.db"), QLatin1String("digikam4.db"), QDir::toNativeSeparators(currentDBFile.dir().path())); if (!endWrapSchemaUpdateStep(updateV4toV7(), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating v4 to v6"; // Still set these even in >= 1.4 because 0.10 - 1.3 may want to apply the updates if not set setLegacySettingEntries(); } // Incremental updates, starting from version 5 for (int v = d->currentVersion.toInt() ; v < schemaVersion() ; ++v) { int targetVersion = v + 1; if (!beginWrapSchemaUpdateStep()) { return false; } QString errorMsg = i18n("Failed to update the database schema from version %1 to version %2. " "Please read the error messages printed on the console and " "report this error as a bug at bugs.kde.org. ", d->currentVersion.toInt(), targetVersion); if (!endWrapSchemaUpdateStep(updateToVersion(targetVersion), errorMsg)) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: success updating to version " << d->currentVersion.toInt(); } // add future updates here } return true; } void CoreDbSchemaUpdater::defaultFilterSettings(QStringList& defaultItemFilter, QStringList& defaultVideoFilter, QStringList& defaultAudioFilter) { //NOTE for updating: //When changing anything here, just increment filterSettingsVersion() so that the changes take effect // https://en.wikipedia.org/wiki/Image_file_formats defaultItemFilter << QLatin1String("jpg") << QLatin1String("jpeg") << QLatin1String("jpe") // JPEG << QLatin1String("jp2") << QLatin1String("j2k") << QLatin1String("jpx") << QLatin1String("jpc") << QLatin1String("pgx") // JPEG-2000 << QLatin1String("tif") << QLatin1String("tiff") // TIFF << QLatin1String("png") // PNG << QLatin1String("gif") << QLatin1String("xpm") << QLatin1String("ppm") << QLatin1String("pnm") << QLatin1String("pgf") << QLatin1String("bmp") << QLatin1String("pcx") << QLatin1String("heic") << QLatin1String("heif") << QLatin1String("webp"); // Raster graphics editor containers: https://en.wikipedia.org/wiki/Raster_graphics_editor defaultItemFilter << QLatin1String("xcf") << QLatin1String("psd") << QLatin1String("psb") << QLatin1String("kra") << QLatin1String("ora"); // Raw images: https://en.wikipedia.org/wiki/Raw_image_format defaultItemFilter << DRawDecoder::rawFilesList(); // Video files: https://en.wikipedia.org/wiki/Video_file_format defaultVideoFilter << QLatin1String("mpeg") << QLatin1String("mpg") << QLatin1String("mpo") << QLatin1String("mpe") << QLatin1String("mts") << QLatin1String("vob") // MPEG << QLatin1String("avi") << QLatin1String("divx") // RIFF << QLatin1String("wmv") << QLatin1String("wmf") << QLatin1String("asf") // ASF << QLatin1String("mp4") << QLatin1String("3gp") << QLatin1String("mov") << QLatin1String("3g2") << QLatin1String("m4v") << QLatin1String("m2v") // QuickTime << QLatin1String("mkv") << QLatin1String("webm") // Matroska << QLatin1String("mng"); // Animated PNG image // Audio files: https://en.wikipedia.org/wiki/Audio_file_format defaultAudioFilter << QLatin1String("ogg") << QLatin1String("oga") << QLatin1String("flac") << QLatin1String("wv") << QLatin1String("ape") // Linux audio << QLatin1String("mpc") << QLatin1String("au") // Linux audio << QLatin1String("m4b") << QLatin1String("aax") << QLatin1String("aa") // Book audio << QLatin1String("mp3") << QLatin1String("aac") // MPEG based audio << QLatin1String("m4a") << QLatin1String("m4p") << QLatin1String("caf") << QLatin1String("aiff") // Apple audio << QLatin1String("wma") << QLatin1String("wav"); // Windows audio } void CoreDbSchemaUpdater::defaultIgnoreDirectoryFilterSettings(QStringList& defaultIgnoreDirectoryFilter) { // NOTE: when update this section, // just increment filterSettingsVersion() so that the changes take effect defaultIgnoreDirectoryFilter << QLatin1String("@eaDir"); } bool CoreDbSchemaUpdater::createFilterSettings() { QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter, defaultIgnoreDirectoryFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); defaultIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); d->albumDB->setIgnoreDirectoryFilterSettings(defaultIgnoreDirectoryFilter); d->albumDB->setSetting(QLatin1String("FilterSettingsVersion"), QString::number(filterSettingsVersion())); d->albumDB->setSetting(QLatin1String("DcrawFilterSettingsVersion"), QString::number(DRawDecoder::rawFilesVersion())); return true; } bool CoreDbSchemaUpdater::updateFilterSettings() { QString filterVersion = d->albumDB->getSetting(QLatin1String("FilterSettingsVersion")); QString dcrawFilterVersion = d->albumDB->getSetting(QLatin1String("DcrawFilterSettingsVersion")); if (filterVersion.toInt() < filterSettingsVersion() || dcrawFilterVersion.toInt() < DRawDecoder::rawFilesVersion()) { createFilterSettings(); } return true; } bool CoreDbSchemaUpdater::createDatabase() { if ( createTables() && createIndices() && createTriggers()) { setLegacySettingEntries(); d->currentVersion = schemaVersion(); // if we start with the V2 hash, version 6 is required d->albumDB->setUniqueHashVersion(uniqueHashVersion()); d->currentRequiredVersion = schemaVersion(); /* // Digikam for database version 5 can work with version 6, though not using the new features d->currentRequiredVersion = 5; */ return true; } else { return false; } } bool CoreDbSchemaUpdater::createTables() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateDB"))); } bool CoreDbSchemaUpdater::createIndices() { // TODO: see which more indices are needed // create indices return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateIndices"))); } bool CoreDbSchemaUpdater::createTriggers() { return d->backend->execDBAction(d->backend->getDBAction(QLatin1String("CreateTriggers"))); } bool CoreDbSchemaUpdater::updateUniqueHash() { if (isUniqueHashUpToDate()) { return true; } readVersionSettings(); { CoreDbTransaction transaction; CoreDbAccess().db()->setUniqueHashVersion(uniqueHashVersion()); CollectionScanner scanner; scanner.setNeedFileCount(true); scanner.setUpdateHashHint(); if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); // earlier digikam does not know about the hash if (d->currentRequiredVersion.toInt() < 6) { d->currentRequiredVersion = 6; setVersionSettings(); } } return true; } bool CoreDbSchemaUpdater::performUpdateToVersion(const QString& actionName, int newVersion, int newRequiredVersion) { if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(1); } DbEngineAction updateAction = d->backend->getDBAction(actionName); if (updateAction.name.isNull()) { QString errorMsg = i18n("The database update action cannot be found. Please ensure that " "the dbconfig.xml file of the current version of digiKam is installed " "at the correct place. "); } if (!d->backend->execDBAction(updateAction)) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: schema update to V" << newVersion << "failed!"; // resort to default error message, set above return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Updated schema to version %1.", newVersion)); } d->currentVersion = newVersion; // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash d->currentRequiredVersion = newRequiredVersion; return true; } bool CoreDbSchemaUpdater::updateToVersion(int targetVersion) { if (d->currentVersion != targetVersion-1) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: updateToVersion performs only incremental updates. Called to update from" << d->currentVersion << "to" << targetVersion << ", aborting."; return false; } switch (targetVersion) { case 6: // Digikam for database version 5 can work with version 6, though not using the new features // Note: We do not upgrade the uniqueHash return performUpdateToVersion(QLatin1String("UpdateSchemaFromV5ToV6"), 6, 5); case 7: // Digikam for database version 5 and 6 can work with version 7, though not using the support for video files. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV6ToV7"), 7, 5); // NOTE: If you add a new update step, please check the d->currentVersion at the bottom of updateV4toV7 // If the update already comes with createTables, createTriggers, we don't need the extra update here case 8: // Digikam for database version 7 can work with version 8, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 8, 5); case 9: // Digikam for database version 8 can work with version 9, now using COLLATE utf8_general_ci for MySQL. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV7ToV9"), 9, 5); case 10: // Digikam for database version 9 can work with version 10, remove ImageHaarMatrix table and add manualOrder column. return performUpdateToVersion(QLatin1String("UpdateSchemaFromV9ToV10"), 10, 5); default: qCDebug(DIGIKAM_COREDB_LOG) << "Core database: unsupported update to version" << targetVersion; return false; } } bool CoreDbSchemaUpdater::copyV3toV4(const QString& digikam3DBPath, const QString& currentDBPath) { if (d->observer) { d->observer->moreSchemaUpdateSteps(2); } d->backend->close(); // We cannot use KIO here because KIO only works from the main thread QFile oldFile(digikam3DBPath); QFile newFile(currentDBPath); // QFile won't override. Remove the empty db file created when a non-existent file is opened newFile.remove(); if (!oldFile.copy(currentDBPath)) { QString errorMsg = i18n("Failed to copy the old database file (\"%1\") " "to its new location (\"%2\"). " "Error message: \"%3\". " "Please make sure that the file can be copied, " "or delete it.", digikam3DBPath, currentDBPath, oldFile.errorString()); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Copied database file")); } if (!d->backend->open(d->parameters)) { QString errorMsg = i18n("The old database file (\"%1\") has been copied " "to the new location (\"%2\") but it cannot be opened. " "Please delete both files and try again, " "starting with an empty database. ", digikam3DBPath, currentDBPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { d->observer->schemaUpdateProgress(i18n("Opened new database file")); } d->currentVersion = 4; return true; } static QStringList cleanUserFilterString(const QString& filterString) { // splits by either ; or space, removes "*.", trims QStringList filterList; QString wildcard(QLatin1String("*.")); QChar dot(QLatin1Char('.')); Q_UNUSED(dot); QChar sep(QLatin1Char(';')); int i = filterString.indexOf( sep ); if ( i == -1 && filterString.indexOf(QLatin1Char(' ')) != -1 ) { sep = QChar(QLatin1Char(' ')); } QStringList sepList = filterString.split(sep, QString::SkipEmptyParts); - foreach(const QString& f, sepList) + foreach (const QString& f, sepList) { if (f.startsWith(wildcard)) { filterList << f.mid(2).trimmed().toLower(); } else { filterList << f.trimmed().toLower(); } } return filterList; } bool CoreDbSchemaUpdater::updateV4toV7() { qCDebug(DIGIKAM_COREDB_LOG) << "Core database : running updateV4toV7"; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->moreSchemaUpdateSteps(11); } // This update was introduced from digikam version 0.9 to digikam 0.10 // We operator on an SQLite3 database under a transaction (which will be rolled back on error) // --- Make space for new tables --- if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Albums RENAME TO AlbumsV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Images RENAME TO ImagesV3;"))) { return false; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: moved tables"; // --- Drop some triggers and indices --- // Don't check for errors here. The "IF EXISTS" clauses seem not supported in SQLite d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_album;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tag;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER insert_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP TRIGGER move_tagstree;")); d->backend->execSql(QString::fromUtf8("DROP INDEX dir_index;")); d->backend->execSql(QString::fromUtf8("DROP INDEX tag_index;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Prepared table creation")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: dropped triggers"; // --- Create new tables --- if (!createTables() || !createIndices()) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Created tables")); } // --- Populate AlbumRoots (from config) --- KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Album Settings")); QString albumLibraryPath = group.readEntry(QLatin1String("Album Path"), QString()); if (albumLibraryPath.isEmpty()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: Album library path from config file is empty. Aborting update."; QString errorMsg = i18n("No album library path has been found in the configuration file. " "Giving up the schema updating process. " "Please try with an empty database, or repair your configuration."); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } QUrl albumLibrary(QUrl::fromLocalFile(albumLibraryPath)); CollectionLocation location = CollectionManager::instance()->addLocation(albumLibrary, albumLibrary.fileName()); if (location.isNull()) { qCDebug(DIGIKAM_COREDB_LOG) << "Core database: failure to create a collection location. Aborting update."; QString errorMsg = i18n("There was an error associating your albumLibraryPath (\"%1\") " "with a storage volume of your system. " "This problem may indicate that there is a problem with your installation. " "If you are working on Linux, check that HAL is installed and running. " "In any case, you can seek advice from the digikam developers mailing list (see www.digikam.org/support). " "The database updating process will now be aborted because we do not want " "to create a new database based on false assumptions from a broken installation.", albumLibraryPath); d->lastErrorMessage = errorMsg; d->setError = true; if (d->observer) { d->observer->error(errorMsg); d->observer->finishedSchemaUpdate(InitializationObserver::UpdateErrorMustAbort); } return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Configured one album root")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: inserted album root"; // --- With the album root, populate albums --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Albums " " (id, albumRoot, relativePath, date, caption, collection, icon) " "SELECT id, ?, url, date, caption, collection, icon " " FROM AlbumsV3;" ), location.id()) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported albums")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated albums"; // --- Add images --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Images " " (id, album, name, status, category, modificationDate, fileSize, uniqueHash) " "SELECT id, dirid, name, ?, ?, NULL, NULL, NULL" " FROM ImagesV3;" ), DatabaseItem::Visible, DatabaseItem::UndefinedCategory) ) { return false; } if (!d->dbAccess->backend()->execSql(QString::fromUtf8( "REPLACE INTO ImageInformation (imageId) SELECT id FROM Images;")) ) { return false; } // remove orphan images that would not be removed by CollectionScanner d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE album NOT IN (SELECT id FROM Albums);")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported images information")); } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: populated Images"; // --- Port searches --- if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return false; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); QString name = (*it).name; if (name == i18n("Last Search")) { name = i18n("Last Search (0.9)"); } if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, name, query); } } // --- Create triggers --- if (!createTriggers()) { return false; } qCDebug(DIGIKAM_COREDB_LOG) << "Core database: created triggers"; // --- Populate name filters --- createFilterSettings(); // --- Set user settings from config --- QStringList defaultItemFilter, defaultVideoFilter, defaultAudioFilter; defaultFilterSettings(defaultItemFilter, defaultVideoFilter, defaultAudioFilter); QSet configItemFilter, configVideoFilter, configAudioFilter; configItemFilter = cleanUserFilterString(group.readEntry(QLatin1String("File Filter"), QString())).toSet(); configItemFilter += cleanUserFilterString(group.readEntry(QLatin1String("Raw File Filter"), QString())).toSet(); configVideoFilter = cleanUserFilterString(group.readEntry(QLatin1String("Movie File Filter"), QString())).toSet(); configAudioFilter = cleanUserFilterString(group.readEntry(QLatin1String("Audio File Filter"), QString())).toSet(); // remove those that are included in the default filter configItemFilter.subtract(defaultItemFilter.toSet()); configVideoFilter.subtract(defaultVideoFilter.toSet()); configAudioFilter.subtract(defaultAudioFilter.toSet()); d->albumDB->setUserFilterSettings(configItemFilter.toList(), configVideoFilter.toList(), configAudioFilter.toList()); qCDebug(DIGIKAM_COREDB_LOG) << "Core database: set initial filter settings with user settings" << configItemFilter; if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Initialized and imported file suffix filter")); } // --- do a full scan --- CollectionScanner scanner; if (d->observer) { d->observer->connectCollectionScanner(&scanner); scanner.setObserver(d->observer); } scanner.completeScan(); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Did the initial full scan")); } // --- Port date, comment and rating (_after_ the scan) --- // Port ImagesV3.date -> ImageInformation.creationDate if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " creationDate=(SELECT datetime FROM ImagesV3 WHERE ImagesV3.id=ImageInformation.imageid) " "WHERE imageid IN (SELECT id FROM ImagesV3);") ) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported creation dates")); } // Port ImagesV3.comment to ItemComments // An author of NULL will inhibt the UNIQUE restriction to take effect (but #189080). Work around. d->backend->execSql(QString::fromUtf8( "DELETE FROM ImageComments WHERE " "type=? AND language=? AND author IS NULL " "AND imageid IN ( SELECT id FROM ImagesV3 ); "), (int)DatabaseComment::Comment, QLatin1String("x-default")); if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO ImageComments " " (imageid, type, language, comment) " "SELECT id, ?, ?, caption FROM ImagesV3;" ), (int)DatabaseComment::Comment, QLatin1String("x-default")) ) { return false; } if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported comments")); } // Port rating storage in ImageProperties to ItemInformation if (!d->backend->execSql(QString::fromUtf8( "UPDATE ImageInformation SET " " rating=(SELECT value FROM ImageProperties " " WHERE ImageInformation.imageid=ImageProperties.imageid AND ImageProperties.property=?) " "WHERE imageid IN (SELECT imageid FROM ImageProperties WHERE property=?);" ), QString::fromUtf8("Rating"), QString::fromUtf8("Rating")) ) { return false; } d->backend->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"), QString::fromUtf8("Rating")); d->backend->execSql(QString::fromUtf8("UPDATE ImageInformation SET rating=0 WHERE rating<0;")); if (d->observer) { if (!d->observer->continueQuery()) { return false; } d->observer->schemaUpdateProgress(i18n("Imported ratings")); } // --- Drop old tables --- d->backend->execSql(QString::fromUtf8("DROP TABLE ImagesV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE AlbumsV3;")); d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); if (d->observer) { d->observer->schemaUpdateProgress(i18n("Dropped v3 tables")); } d->currentRequiredVersion = 5; d->currentVersion = 7; qCDebug(DIGIKAM_COREDB_LOG) << "Core database: returning true from updating to 5"; return true; } void CoreDbSchemaUpdater::setLegacySettingEntries() { d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } // ---------- Legacy code ------------ void CoreDbSchemaUpdater::preAlpha010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update1")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE Searches RENAME TO SearchesV3;"))) { return; } if ( !d->backend->execSql( QString::fromUtf8( "CREATE TABLE IF NOT EXISTS Searches \n" " (id INTEGER PRIMARY KEY, \n" " type INTEGER, \n" " name TEXT NOT NULL, \n" " query TEXT NOT NULL);" ) )) { return; } if (!d->backend->execSql(QString::fromUtf8( "REPLACE INTO Searches " " (id, type, name, query) " "SELECT id, ?, name, url" " FROM SearchesV3;"), DatabaseSearch::LegacyUrlSearch) ) { return; } SearchInfo::List sList = d->albumDB->scanSearches(); for (SearchInfo::List::const_iterator it = sList.constBegin(); it != sList.constEnd(); ++it) { QUrl url((*it).query); ItemQueryBuilder builder; QString query = builder.convertFromUrlToXml(url); if (QUrlQuery(url).queryItemValue(QLatin1String("type")) == QLatin1String("datesearch")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::TimeLineSearch, (*it).name, query); } else if (QUrlQuery(url).queryItemValue(QLatin1String("1.key")) == QLatin1String("keyword")) { d->albumDB->updateSearch((*it).id, DatabaseSearch::KeywordSearch, (*it).name, query); } else { d->albumDB->updateSearch((*it).id, DatabaseSearch::AdvancedSearch, (*it).name, query); } } d->backend->execSql(QString::fromUtf8("DROP TABLE SearchesV3;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update2")); if (!hasUpdate.isNull()) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImagePositions RENAME TO ItemPositionsTemp;"))) { return; } if (!d->backend->execSql(QString::fromUtf8("ALTER TABLE ImageMetadata RENAME TO ImageMetadataTemp;"))) { return; } d->backend->execSql( QString::fromUtf8("CREATE TABLE ItemPositions\n" " (imageid INTEGER PRIMARY KEY,\n" " latitude TEXT,\n" " latitudeNumber REAL,\n" " longitude TEXT,\n" " longitudeNumber REAL,\n" " altitude REAL,\n" " orientation REAL,\n" " tilt REAL,\n" " roll REAL,\n" " accuracy REAL,\n" " description TEXT);") ); d->backend->execSql(QString::fromUtf8("REPLACE INTO ImagePositions " " (imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, accuracy, description) " "SELECT imageid, latitude, latitudeNumber, longitude, longitudeNumber, " " altitude, orientation, tilt, roll, 0, description " " FROM ItemPositionsTemp;")); d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageMetadata\n" " (imageid INTEGER PRIMARY KEY,\n" " make TEXT,\n" " model TEXT,\n" " lens TEXT,\n" " aperture REAL,\n" " focalLength REAL,\n" " focalLength35 REAL,\n" " exposureTime REAL,\n" " exposureProgram INTEGER,\n" " exposureMode INTEGER,\n" " sensitivity INTEGER,\n" " flash INTEGER,\n" " whiteBalance INTEGER,\n" " whiteBalanceColorTemperature INTEGER,\n" " meteringMode INTEGER,\n" " subjectDistance REAL,\n" " subjectDistanceCategory INTEGER);") ); d->backend->execSql( QString::fromUtf8("INSERT INTO ImageMetadata " " (imageid, make, model, lens, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) " "SELECT imageid, make, model, NULL, aperture, focalLength, focalLength35, " " exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, " " whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory " "FROM ImageMetadataTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ItemPositionsTemp;")); d->backend->execSql(QString::fromUtf8("DROP TABLE ImageMetadataTemp;")); d->albumDB->setSetting(QLatin1String("preAlpha010Update2"), QLatin1String("true")); } void CoreDbSchemaUpdater::preAlpha010Update3() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("preAlpha010Update3")); if (!hasUpdate.isNull()) { return; } d->backend->execSql(QString::fromUtf8("DROP TABLE ItemCopyright;")); d->backend->execSql(QString::fromUtf8("CREATE TABLE ItemCopyright\n" " (imageid INTEGER,\n" " property TEXT,\n" " value TEXT,\n" " extraValue TEXT,\n" " UNIQUE(imageid, property, value, extraValue));") ); d->albumDB->setSetting(QLatin1String("preAlpha010Update3"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update1() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update1")); if (!hasUpdate.isNull()) { return; } // if Image has been deleted d->backend->execSql(QString::fromUtf8("DROP TRIGGER delete_image;")); d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageHaarMatrix\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemInformation\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageMetadata\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemPositions\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemComments\n " " WHERE imageid=OLD.id;\n" " DELETE From ItemCopyright\n " " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); d->albumDB->setSetting(QLatin1String("beta010Update1"), QLatin1String("true")); } void CoreDbSchemaUpdater::beta010Update2() { QString hasUpdate = d->albumDB->getSetting(QLatin1String("beta010Update2")); if (!hasUpdate.isNull()) { return; } // force rescan and creation of ImageInformation entry for videos and audio d->backend->execSql(QString::fromUtf8("DELETE FROM Images WHERE category=2 OR category=3;")); d->albumDB->setSetting(QLatin1String("beta010Update2"), QLatin1String("true")); } bool CoreDbSchemaUpdater::createTablesV3() { if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Albums\n" " (id INTEGER PRIMARY KEY,\n" " url TEXT NOT NULL UNIQUE,\n" " date DATE NOT NULL,\n" " caption TEXT,\n" " collection TEXT,\n" " icon INTEGER);") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Tags\n" " (id INTEGER PRIMARY KEY,\n" " pid INTEGER,\n" " name TEXT NOT NULL,\n" " icon INTEGER,\n" " iconkde TEXT,\n" " UNIQUE (name, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE TagsTree\n" " (id INTEGER NOT NULL,\n" " pid INTEGER NOT NULL,\n" " UNIQUE (id, pid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Images\n" " (id INTEGER PRIMARY KEY,\n" " name TEXT NOT NULL,\n" " dirid INTEGER NOT NULL,\n" " caption TEXT,\n" " datetime DATETIME,\n" " UNIQUE (name, dirid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageTags\n" " (imageid INTEGER NOT NULL,\n" " tagid INTEGER NOT NULL,\n" " UNIQUE (imageid, tagid));") )) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE ImageProperties\n" " (imageid INTEGER NOT NULL,\n" " property TEXT NOT NULL,\n" " value TEXT NOT NULL,\n" " UNIQUE (imageid, property));") )) { return false; } if ( !d->backend->execSql( QString::fromUtf8("CREATE TABLE Searches \n" " (id INTEGER PRIMARY KEY, \n" " name TEXT NOT NULL UNIQUE, \n" " url TEXT NOT NULL);") ) ) { return false; } if (!d->backend->execSql( QString::fromUtf8("CREATE TABLE Settings \n" "(keyword TEXT NOT NULL UNIQUE,\n" " value TEXT);") )) { return false; } // TODO: see which more indices are needed // create indices d->backend->execSql(QString::fromUtf8("CREATE INDEX dir_index ON Images (dirid);")); d->backend->execSql(QString::fromUtf8("CREATE INDEX tag_index ON ImageTags (tagid);")); // create triggers // trigger: delete from Images/ImageTags/ImageProperties // if Album has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_album DELETE ON Albums\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE From ImageProperties\n" " WHERE imageid IN (SELECT id FROM Images WHERE dirid=OLD.id);\n" " DELETE FROM Images\n" " WHERE dirid = OLD.id;\n" "END;")); // trigger: delete from ImageTags/ImageProperties // if Image has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_image DELETE ON Images\n" "BEGIN\n" " DELETE FROM ImageTags\n" " WHERE imageid=OLD.id;\n" " DELETE From ImageProperties\n " " WHERE imageid=OLD.id;\n" " UPDATE Albums SET icon=null \n " " WHERE icon=OLD.id;\n" " UPDATE Tags SET icon=null \n " " WHERE icon=OLD.id;\n" "END;")); // trigger: delete from ImageTags if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tag DELETE ON Tags\n" "BEGIN\n" " DELETE FROM ImageTags WHERE tagid=OLD.id;\n" "END;")); // trigger: insert into TagsTree if Tag has been added d->backend->execSql(QString::fromUtf8("CREATE TRIGGER insert_tagstree AFTER INSERT ON Tags\n" "BEGIN\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER delete_tagstree DELETE ON Tags\n" "BEGIN\n" " DELETE FROM Tags\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id IN (SELECT id FROM TagsTree WHERE pid=OLD.id);\n" " DELETE FROM TagsTree\n" " WHERE id=OLD.id;\n" "END;")); // trigger: delete from TagsTree if Tag has been deleted d->backend->execSql(QString::fromUtf8("CREATE TRIGGER move_tagstree UPDATE OF pid ON Tags\n" "BEGIN\n" " DELETE FROM TagsTree\n" " WHERE\n" " ((id = OLD.id)\n" " OR\n" " id IN (SELECT id FROM TagsTree WHERE pid=OLD.id))\n" " AND\n" " pid IN (SELECT pid FROM TagsTree WHERE id=OLD.id);\n" " INSERT INTO TagsTree\n" " SELECT NEW.id, NEW.pid\n" " UNION\n" " SELECT NEW.id, pid FROM TagsTree WHERE id=NEW.pid\n" " UNION\n" " SELECT id, NEW.pid FROM TagsTree WHERE pid=NEW.id\n" " UNION\n" " SELECT A.id, B.pid FROM TagsTree A, TagsTree B\n" " WHERE\n" " A.pid = NEW.id AND B.id = NEW.pid;\n" "END;")); return true; } } // namespace Digikam diff --git a/core/libs/database/engine/dbenginebackend.cpp b/core/libs/database/engine/dbenginebackend.cpp index 8107c50f7e..473d02f0a1 100644 --- a/core/libs/database/engine/dbenginebackend.cpp +++ b/core/libs/database/engine/dbenginebackend.cpp @@ -1,1782 +1,1782 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-04-15 * Description : Database engine abstract database backend * * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "dbenginebackend.h" #include "dbenginebackend_p.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dbengineactiontype.h" namespace Digikam { DbEngineLocking::DbEngineLocking() : mutex(QMutex::Recursive), lockCount(0) // create a recursive mutex { } // ----------------------------------------------------------------------------------------- BdEngineBackendPrivate::BusyWaiter::BusyWaiter(BdEngineBackendPrivate* const d) : AbstractWaitingUnlocker(d, &d->busyWaitMutex, &d->busyWaitCondVar) { } // ----------------------------------------------------------------------------------------- BdEngineBackendPrivate::ErrorLocker::ErrorLocker(BdEngineBackendPrivate* const d) : AbstractWaitingUnlocker(d, &d->errorLockMutex, &d->errorLockCondVar) { } // ----------------------------------------------------------------------------------------- DbEngineThreadData::DbEngineThreadData() : valid(0), transactionCount(0) { } DbEngineThreadData::~DbEngineThreadData() { if (transactionCount) { qCDebug(DIGIKAM_DBENGINE_LOG) << "WARNING !!! Transaction count is" << transactionCount << "when destroying database!!!"; } closeDatabase(); } void DbEngineThreadData::closeDatabase() { QString connectionToRemove; if (database.isOpen()) { connectionToRemove = database.connectionName(); } // Destroy object database = QSqlDatabase(); valid = 0; transactionCount = 0; lastError = QSqlError(); // Remove connection if (!connectionToRemove.isNull()) { QSqlDatabase::removeDatabase(connectionToRemove); } } BdEngineBackendPrivate::BdEngineBackendPrivate(BdEngineBackend* const backend) : currentValidity(0), isInTransaction(false), status(BdEngineBackend::Unavailable), lock(nullptr), operationStatus(BdEngineBackend::ExecuteNormal), errorLockOperationStatus(BdEngineBackend::ExecuteNormal), errorHandler(nullptr), q(backend) { } BdEngineBackendPrivate::~BdEngineBackendPrivate() { // Must be shut down from the main thread. // Clean up the QThreadStorage. It deletes any stored data. threadDataStorage.setLocalData(0); } void BdEngineBackendPrivate::init(const QString& name, DbEngineLocking* const l) { backendName = name; lock = l; qRegisterMetaType("DbEngineErrorAnswer*"); qRegisterMetaType(); } // "A connection can only be used from within the thread that created it. // Moving connections between threads or creating queries from a different thread is not supported." // => one QSqlDatabase object per thread. // The main class' open/close methods only interact with the "firstDatabase" object. // When another thread requests a DB, a new connection is opened and closed at // finishing of the thread. QSqlDatabase BdEngineBackendPrivate::databaseForThread() { DbEngineThreadData* threadData = nullptr; if (!threadDataStorage.hasLocalData()) { threadData = new DbEngineThreadData; threadDataStorage.setLocalData(threadData); } else { threadData = threadDataStorage.localData(); } // do we need to reopen the database because parameter changed and validity was increased? if (threadData->valid && threadData->valid < currentValidity) { threadData->closeDatabase(); } if (!threadData->valid || !threadData->database.isOpen()) { threadData->database = createDatabaseConnection(); if (threadData->database.open()) { threadData->valid = currentValidity; } else { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while opening the database. Error was" << threadData->database.lastError(); } } return threadData->database; } QSqlDatabase BdEngineBackendPrivate::createDatabaseConnection() { QSqlDatabase db = QSqlDatabase::addDatabase(parameters.databaseType, connectionName()); QString connectOptions = parameters.connectOptions; if (parameters.isSQLite()) { QStringList toAdd; // enable shared cache, especially useful with SQLite >= 3.5.0 toAdd << QLatin1String("QSQLITE_ENABLE_SHARED_CACHE"); // We do our own waiting. toAdd << QLatin1String("QSQLITE_BUSY_TIMEOUT=0"); if (!connectOptions.isEmpty()) { connectOptions += QLatin1Char(';'); } connectOptions += toAdd.join(QLatin1Char(';')); } db.setDatabaseName(parameters.databaseNameCore); db.setConnectOptions(connectOptions); db.setHostName(parameters.hostName); db.setPort(parameters.port); db.setUserName(parameters.userName); db.setPassword(parameters.password); return db; } void BdEngineBackendPrivate::closeDatabaseForThread() { if (threadDataStorage.hasLocalData()) { threadDataStorage.localData()->closeDatabase(); } } QSqlError BdEngineBackendPrivate::databaseErrorForThread() { if (threadDataStorage.hasLocalData()) { return threadDataStorage.localData()->lastError; } return QSqlError(); } void BdEngineBackendPrivate::setDatabaseErrorForThread(const QSqlError& lastError) { if (threadDataStorage.hasLocalData()) { threadDataStorage.localData()->lastError = lastError; } } QString BdEngineBackendPrivate::connectionName() { return backendName + QString::number((quintptr)QThread::currentThread()); } bool BdEngineBackendPrivate::incrementTransactionCount() { return (!threadDataStorage.localData()->transactionCount++); } bool BdEngineBackendPrivate::decrementTransactionCount() { return (!--threadDataStorage.localData()->transactionCount); } bool BdEngineBackendPrivate::isInMainThread() const { return QThread::currentThread() == QCoreApplication::instance()->thread(); } bool BdEngineBackendPrivate::isInUIThread() const { QApplication* const app = qobject_cast(QCoreApplication::instance()); if (!app) { return false; } return (QThread::currentThread() == app->thread()); } bool BdEngineBackendPrivate::reconnectOnError() const { return parameters.isMySQL(); } bool BdEngineBackendPrivate::isSQLiteLockError(const DbEngineSqlQuery& query) const { return parameters.isSQLite() && (query.lastError().nativeErrorCode() == QLatin1String("5") /*SQLITE_BUSY*/ || query.lastError().nativeErrorCode() == QLatin1String("6") /*SQLITE_LOCKED*/); } bool BdEngineBackendPrivate::isSQLiteLockTransactionError(const QSqlError& lastError) const { return ( parameters.isSQLite() && lastError.type() == QSqlError::TransactionError && lastError.databaseText() == QLatin1String("database is locked") ); // wouldnt it be great if they gave us the database error number... } bool BdEngineBackendPrivate::isConnectionError(const DbEngineSqlQuery& query) const { // the backend returns connection error e.g. for Constraint Failed errors. if (parameters.isSQLite()) { return false; } return ( query.lastError().type() == QSqlError::ConnectionError || query.lastError().nativeErrorCode() == QLatin1String("2006") ); } bool BdEngineBackendPrivate::needToConsultUserForError(const DbEngineSqlQuery&) const { // no such conditions found and defined as yet return false; } bool BdEngineBackendPrivate::needToHandleWithErrorHandler(const DbEngineSqlQuery& query) const { return (isConnectionError(query) || needToConsultUserForError(query)); } bool BdEngineBackendPrivate::checkRetrySQLiteLockError(int retries) { if (!(retries % 25)) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Database is locked. Waited" << retries*10; } const int uiMaxRetries = 50; const int maxRetries = 1000; if (retries > qMax(uiMaxRetries, maxRetries)) { if (retries > (isInUIThread() ? uiMaxRetries : maxRetries)) { qCWarning(DIGIKAM_DBENGINE_LOG) << "Detected locked database file. There is an active transaction. Waited but giving up now."; return false; } } BusyWaiter waiter(this); waiter.wait(10); return true; } void BdEngineBackendPrivate::debugOutputFailedQuery(const QSqlQuery& query) const { qCDebug(DIGIKAM_DBENGINE_LOG) << "Failure executing query:\n" << query.executedQuery() << "\nError messages:" << query.lastError().driverText() << query.lastError().databaseText() << query.lastError().nativeErrorCode() << query.lastError().type() << "\nBound values: " << query.boundValues().values(); } void BdEngineBackendPrivate::debugOutputFailedTransaction(const QSqlError& error) const { qCDebug(DIGIKAM_DBENGINE_LOG) << "Failure executing transaction. Error messages:\n" << error.driverText() << error.databaseText() << error.nativeErrorCode() << error.type(); } void BdEngineBackendPrivate::transactionFinished() { // wakes up any BusyWaiter waiting on the busyWaitCondVar. // Possibly called under d->lock->mutex lock, so we do not lock the busyWaitMutex busyWaitCondVar.wakeOne(); } /** Set the wait flag to queryStatus. Typically, call this with Wait. */ void BdEngineBackendPrivate::setQueryOperationFlag(BdEngineBackend::QueryOperationStatus status) { // Enforce lock order (first main mutex, second error lock mutex) QMutexLocker l(&errorLockMutex); // this change must be done under errorLockMutex lock errorLockOperationStatus = status; operationStatus = status; } /** Set the wait flag to queryStatus and wake all waiting threads. * Typically, call wakeAll with status ExecuteNormal or AbortQueries. */ void BdEngineBackendPrivate::queryOperationWakeAll(BdEngineBackend::QueryOperationStatus status) { QMutexLocker l(&errorLockMutex); operationStatus = status; errorLockOperationStatus = status; errorLockCondVar.wakeAll(); } bool BdEngineBackendPrivate::checkOperationStatus() { while (operationStatus == BdEngineBackend::Wait) { ErrorLocker locker(this); locker.wait(); } if (operationStatus == BdEngineBackend::ExecuteNormal) { return true; } else if (operationStatus == BdEngineBackend::AbortQueries) { return false; } return false; } /// Returns true if the query shall be retried bool BdEngineBackendPrivate::handleWithErrorHandler(const DbEngineSqlQuery* const query) { if (errorHandler) { setQueryOperationFlag(BdEngineBackend::Wait); ErrorLocker locker(this); bool called = false; QSqlError lastError = query ? query->lastError() : databaseForThread().lastError(); QString lastQuery = query ? query->lastQuery() : QString(); if (!query || isConnectionError(*query)) { called = QMetaObject::invokeMethod(errorHandler, "connectionError", Qt::AutoConnection, Q_ARG(DbEngineErrorAnswer*, this), Q_ARG(const QSqlError, lastError), Q_ARG(const QString, lastQuery)); } else if (needToConsultUserForError(*query)) { called = QMetaObject::invokeMethod(errorHandler, "consultUserForError", Qt::AutoConnection, Q_ARG(DbEngineErrorAnswer*, this), Q_ARG(const QSqlError, lastError), Q_ARG(const QString, lastQuery)); } else { // unclear what to do. errorLockOperationStatus = BdEngineBackend::ExecuteNormal; operationStatus = BdEngineBackend::ExecuteNormal; return true; } if (called) { locker.wait(); } else { qCWarning(DIGIKAM_DBENGINE_LOG) << "Failed to invoke DbEngineErrorHandler. Aborting all queries."; operationStatus = BdEngineBackend::AbortQueries; } switch (operationStatus) { case BdEngineBackend::ExecuteNormal: case BdEngineBackend::Wait: return true; case BdEngineBackend::AbortQueries: return false; } } else { // TODO check if it's better to use an own error handler for kio slaves. // But for now, close only the database in the hope, that the next // access will be successful. closeDatabaseForThread(); } return false; } void BdEngineBackendPrivate::connectionErrorContinueQueries() { // Attention: called from out of context, maybe without any lock QMutexLocker l(&lock->mutex); queryOperationWakeAll(BdEngineBackend::ExecuteNormal); } void BdEngineBackendPrivate::connectionErrorAbortQueries() { // Attention: called from out of context, maybe without any lock QMutexLocker l(&lock->mutex); queryOperationWakeAll(BdEngineBackend::AbortQueries); } // ----------------------------------------------------------------------------------------- BdEngineBackendPrivate::AbstractUnlocker::AbstractUnlocker(BdEngineBackendPrivate* const d) : count(0), d(d) { // Why two mutexes? The main mutex is recursive and won't work with a condvar. // acquire lock d->lock->mutex.lock(); // store lock count count = d->lock->lockCount; // set lock count to 0 d->lock->lockCount = 0; // unlock for (int i = 0 ; i < count ; ++i) { d->lock->mutex.unlock(); } } void BdEngineBackendPrivate::AbstractUnlocker::finishAcquire() { // drop lock acquired in first line. Main mutex is now free. // We maintain lock order (first main mutex, second error lock mutex) // but we drop main mutex lock for waiting on the cond var. d->lock->mutex.unlock(); } BdEngineBackendPrivate::AbstractUnlocker::~AbstractUnlocker() { // lock main mutex as often as it was locked before for (int i = 0 ; i < count ; ++i) { d->lock->mutex.lock(); } // update lock count d->lock->lockCount += count; } // ----------------------------------------------------------------------------------------- BdEngineBackendPrivate::AbstractWaitingUnlocker::AbstractWaitingUnlocker(BdEngineBackendPrivate* const d, QMutex* const mutex, QWaitCondition* const condVar) : AbstractUnlocker(d), mutex(mutex), condVar(condVar) { // Why two mutexes? The main mutex is recursive and won't work with a condvar. // lock condvar mutex (lock only if main mutex is locked) mutex->lock(); finishAcquire(); } BdEngineBackendPrivate::AbstractWaitingUnlocker::~AbstractWaitingUnlocker() { // unlock condvar mutex. Both mutexes are now free. mutex->unlock(); // now base class destructor is executed, reallocating main mutex } bool BdEngineBackendPrivate::AbstractWaitingUnlocker::wait(unsigned long time) { return condVar->wait(mutex, time); } // ----------------------------------------------------------------------------------------- /** This suspends the current thread if the query status as * set by setFlag() is Wait and until the thread is woken with wakeAll(). * The CoreDbAccess mutex will be unlocked while waiting. */ void BdEngineBackendPrivate::ErrorLocker::wait() { // we use a copy of the flag under lock of the errorLockMutex to be able to check it here while (d->errorLockOperationStatus == BdEngineBackend::Wait) { wait(); } } // ----------------------------------------------------------------------------------------- BdEngineBackend::BdEngineBackend(const QString& backendName, DbEngineLocking* const locking) : d_ptr(new BdEngineBackendPrivate(this)) { d_ptr->init(backendName, locking); } BdEngineBackend::BdEngineBackend(const QString& backendName, DbEngineLocking* const locking, BdEngineBackendPrivate& dd) : d_ptr(&dd) { d_ptr->init(backendName, locking); } BdEngineBackend::~BdEngineBackend() { Q_D(BdEngineBackend); close(); delete d; } DbEngineConfigSettings BdEngineBackend::configElement() const { Q_D(const BdEngineBackend); return DbEngineConfig::element(d->parameters.databaseType); } DbEngineAction BdEngineBackend::getDBAction(const QString& actionName) const { Q_D(const BdEngineBackend); DbEngineAction action = configElement().sqlStatements.value(actionName); if (action.name.isNull()) { qCWarning(DIGIKAM_DBENGINE_LOG) << "No DB action defined for" << actionName << "! Implementation missing for this database type (" << d->parameters.databaseType << ")."; } return action; } BdEngineBackend::DbType BdEngineBackend::databaseType() const { Q_D(const BdEngineBackend); return d->parameters.isSQLite() ? DbType::SQLite : DbType::MySQL; } BdEngineBackend::QueryState BdEngineBackend::execDBAction(const DbEngineAction& action, QList* const values, QVariant* const lastInsertId) { return execDBAction(action, QMap(), values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execDBAction(const QString& action, QList* const values, QVariant* const lastInsertId) { return execDBAction(getDBAction(action), QMap(), values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execDBAction(const QString& action, const QMap& bindingMap, QList* const values, QVariant* const lastInsertId) { return execDBAction(getDBAction(action), bindingMap, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execDBAction(const DbEngineAction& action, const QMap& bindingMap, QList* const values, QVariant* const lastInsertId) { Q_D(BdEngineBackend); BdEngineBackend::QueryState returnResult = BdEngineBackend::QueryState(BdEngineBackend::NoErrors); QSqlDatabase db = d->databaseForThread(); if (action.name.isNull()) { qCWarning(DIGIKAM_DBENGINE_LOG) << "Attempt to execute null action"; return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } //qCDebug(DIGIKAM_DBENGINE_LOG) << "Executing DBAction ["<< action.name <<"]"; bool wrapInTransaction = (action.mode == QLatin1String("transaction")); if (wrapInTransaction) { beginTransaction(); } - foreach(const DbEngineActionElement& actionElement, action.dbActionElements) + foreach (const DbEngineActionElement& actionElement, action.dbActionElements) { BdEngineBackend::QueryState result; if (actionElement.mode == QLatin1String("query")) { result = execSql(actionElement.statement, bindingMap, values, lastInsertId); } else if (actionElement.mode == QLatin1String("unprepared")) { result = execDirectSqlWithResult(actionElement.statement, values, lastInsertId); } else { result = execDirectSql(actionElement.statement); } if (result != BdEngineBackend::NoErrors) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while executing DBAction [" << action.name << "] Statement [" << actionElement.statement << "]"; returnResult = result; /* if (wrapInTransaction && !db.rollback()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while rollback changes of previous DBAction."; } */ break; } } if (wrapInTransaction) { commitTransaction(); } /* if (returnResult == BdEngineBackend::NoErrors && wrapInTransaction && !db.commit()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while committing changes of previous DBAction."; } */ return returnResult; } QSqlQuery BdEngineBackend::execDBActionQuery(const QString& action, const QMap& bindingMap) { return execDBActionQuery(getDBAction(action), bindingMap); } QSqlQuery BdEngineBackend::execDBActionQuery(const DbEngineAction& action, const QMap& bindingMap) { Q_D(BdEngineBackend); QSqlDatabase db = d->databaseForThread(); // qCDebug(DIGIKAM_DBENGINE_LOG) << "Executing DBAction ["<< action.name <<"]"; QSqlQuery result; foreach (const DbEngineActionElement& actionElement, action.dbActionElements) { if (actionElement.mode == QLatin1String("query")) { result = execQuery(actionElement.statement, bindingMap); } else { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error, only DBActions with mode 'query' are allowed at this call!"; } if (result.lastError().isValid() && !result.lastError().nativeErrorCode().isEmpty()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while executing DBAction [" << action.name << "] Statement [" << actionElement.statement << "] Errornr. [" << result.lastError() << "]"; break; } } return result; } void BdEngineBackend::setDbEngineErrorHandler(DbEngineErrorHandler* const handler) { Q_D(BdEngineBackend); delete d->errorHandler; d->errorHandler = handler; } bool BdEngineBackend::isCompatible(const DbEngineParameters& parameters) { return QSqlDatabase::drivers().contains(parameters.databaseType); } bool BdEngineBackend::open(const DbEngineParameters& parameters) { Q_D(BdEngineBackend); d->parameters = parameters; // This will make possibly opened thread dbs reload at next access d->currentValidity++; int retries = 0; forever { QSqlDatabase database = d->databaseForThread(); if (!database.isOpen()) { //qCDebug(DIGIKAM_DBENGINE_LOG) << "Error while opening the database. Trying again."; if (connectionErrorHandling(retries++)) { continue; } else { return false; } } else { break; } } d->status = Open; return true; } void BdEngineBackend::close() { Q_D(BdEngineBackend); d->closeDatabaseForThread(); d->status = Unavailable; } BdEngineBackend::Status BdEngineBackend::status() const { Q_D(const BdEngineBackend); return d->status; } /* bool BdEngineBackend::execSql(const QString& sql, QStringList* const values) { QSqlQuery query = execQuery(sql); if (!query.isActive()) return false; if (!values) return true; int count = query.record().count(); while (query.next()) { for (int i=0; i BdEngineBackend::readToList(DbEngineSqlQuery& query) { QList list; QSqlRecord record = query.record(); int count = record.count(); while (query.next()) { for (int i = 0 ; i < count ; ++i) { list << query.value(i); } } // qCDebug(DIGIKAM_DBENGINE_LOG) << "Setting result value list ["<< list <<"]"; return list; } BdEngineBackend::QueryState BdEngineBackend::handleQueryResult(DbEngineSqlQuery& query, QList* const values, QVariant* const lastInsertId) { if (!query.isActive()) { if (query.lastError().type() == QSqlError::ConnectionError) { return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); } else { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } } if (lastInsertId) { (*lastInsertId) = query.lastInsertId(); } if (values) { (*values) = readToList(query); } return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); } // ------------------------------------------------------------------------------------- BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QVariant& boundValue1, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, boundValue1); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QVariant& boundValue1, const QVariant& boundValue2, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2, boundValue3); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3, const QVariant& boundValue4, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, boundValue1, boundValue2, boundValue3, boundValue4); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QList& boundValues, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, boundValues); return handleQueryResult(query, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(const QString& sql, const QMap& bindingMap, QList* const values, QVariant* const lastInsertId) { DbEngineSqlQuery query = execQuery(sql, bindingMap); return handleQueryResult(query, values, lastInsertId); } // ------------------------------------------------------------------------------------- BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, QList* const values, QVariant* const lastInsertId) { exec(preparedQuery); return handleQueryResult(preparedQuery, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1, QList* const values, QVariant* const lastInsertId) { execQuery(preparedQuery, boundValue1); return handleQueryResult(preparedQuery, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1, const QVariant& boundValue2, QList* const values, QVariant* const lastInsertId) { execQuery(preparedQuery, boundValue1, boundValue2); return handleQueryResult(preparedQuery, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3, QList* const values, QVariant* const lastInsertId) { execQuery(preparedQuery, boundValue1, boundValue2, boundValue3); return handleQueryResult(preparedQuery, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3, const QVariant& boundValue4, QList* const values, QVariant* const lastInsertId) { execQuery(preparedQuery, boundValue1, boundValue2, boundValue3, boundValue4); return handleQueryResult(preparedQuery, values, lastInsertId); } BdEngineBackend::QueryState BdEngineBackend::execSql(DbEngineSqlQuery& preparedQuery, const QList& boundValues, QList* const values, QVariant* const lastInsertId) { execQuery(preparedQuery, boundValues); return handleQueryResult(preparedQuery, values, lastInsertId); } // ------------------------------------------------------------------------------------- DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, const QVariant& boundValue1) { DbEngineSqlQuery query = prepareQuery(sql); // qCDebug(DIGIKAM_DBENGINE_LOG) << "Trying to sql ["<< sql <<"] query ["<& boundValues) { DbEngineSqlQuery query = prepareQuery(sql); execQuery(query, boundValues); return query; } DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql) { DbEngineSqlQuery query = prepareQuery(sql); // qCDebug(DIGIKAM_DBENGINE_LOG)<<"execQuery: Using statement ["<< query.lastQuery() <<"]"; exec(query); return query; } // ------------------------------------------------------------------------------------- void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QVariant& boundValue1) { query.bindValue(0, boundValue1); exec(query); } void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QVariant& boundValue1, const QVariant& boundValue2) { query.bindValue(0, boundValue1); query.bindValue(1, boundValue2); exec(query); } void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3) { query.bindValue(0, boundValue1); query.bindValue(1, boundValue2); query.bindValue(2, boundValue3); exec(query); } void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3, const QVariant& boundValue4) { query.bindValue(0, boundValue1); query.bindValue(1, boundValue2); query.bindValue(2, boundValue3); query.bindValue(3, boundValue4); exec(query); } void BdEngineBackend::execQuery(DbEngineSqlQuery& query, const QList& boundValues) { for (int i = 0 ; i < boundValues.size() ; ++i) { query.bindValue(i, boundValues.at(i)); } exec(query); } // ------------------------------------------------------------------------------------- DbEngineSqlQuery BdEngineBackend::execQuery(const QString& sql, const QMap& bindingMap) { QString preparedString = sql; QList valuesToBind; if (!bindingMap.isEmpty()) { // qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepare statement [" << preparedString << "] with binding map [" << bindingMap << "]"; QRegExp identifierRegExp(QLatin1String(":[A-Za-z0-9]+")); int pos = 0; while ( (pos=identifierRegExp.indexIn(preparedString, pos)) != -1) { QString namedPlaceholder = identifierRegExp.cap(0); if (!bindingMap.contains(namedPlaceholder)) { qCWarning(DIGIKAM_DBENGINE_LOG) << "Missing place holder" << namedPlaceholder << "in binding map. The following values are defined for this action:" << bindingMap.keys() <<". This is a setup error!"; //TODO What should we do here? How can we cancel that action? } QVariant placeHolderValue = bindingMap.value(namedPlaceholder); QString replaceStr; if (placeHolderValue.userType() == qMetaTypeId()) { DbEngineActionType actionType = placeHolderValue.value(); bool isValue = actionType.isValue(); QVariant value = actionType.getActionValue(); if ( value.type() == QVariant::Map ) { QMap placeHolderMap = value.toMap(); QMap::const_iterator iterator; for (iterator = placeHolderMap.constBegin(); iterator != placeHolderMap.constEnd(); ++iterator) { const QString& key = iterator.key(); const QVariant& value = iterator.value(); replaceStr.append(key); replaceStr.append(QLatin1String("= ?")); valuesToBind.append(value); // Add a semicolon to the statement, if we are not on the last entry if ((iterator+1) != placeHolderMap.constEnd()) { replaceStr.append(QLatin1String(", ")); } } } else if ( value.type() == QVariant::List ) { QList placeHolderList = value.toList(); QList::const_iterator iterator; for (iterator = placeHolderList.constBegin(); iterator != placeHolderList.constEnd(); ++iterator) { const QVariant& entry = *iterator; if (isValue) { replaceStr.append(QLatin1String("?")); valuesToBind.append(entry); } else { replaceStr.append(entry.value()); } // Add a semicolon to the statement, if we are not on the last entry if ((iterator+1) != placeHolderList.constEnd()) { replaceStr.append(QLatin1String(", ")); } } } else if (value.type() == QVariant::StringList ) { QStringList placeHolderList = value.toStringList(); QStringList::const_iterator iterator; for (iterator = placeHolderList.constBegin(); iterator != placeHolderList.constEnd(); ++iterator) { const QString& entry = *iterator; if (isValue) { replaceStr.append(QLatin1String("?")); valuesToBind.append(entry); } else { replaceStr.append(entry); } // Add a semicolon to the statement, if we are not on the last entry if ((iterator+1) != placeHolderList.constEnd()) { replaceStr.append(QLatin1String(", ")); } } } else { if (isValue) { replaceStr = QLatin1Char('?'); valuesToBind.append(value); } else { replaceStr = value.toString(); } } } else { // qCDebug(DIGIKAM_DBENGINE_LOG) << "Bind key ["<< namedPlaceholder << "] to value [" << bindingMap[namedPlaceholder] << "]"; valuesToBind.append(placeHolderValue); replaceStr = QLatin1Char('?'); } preparedString = preparedString.replace(pos, identifierRegExp.matchedLength(), replaceStr); pos = 0; // reset pos } } // qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepared statement [" << preparedString << "] values [" << valuesToBind << "]"; DbEngineSqlQuery query = prepareQuery(preparedString); for (int i = 0 ; i < valuesToBind.size() ; ++i) { query.bindValue(i, valuesToBind.at(i)); } exec(query); return query; } BdEngineBackend::QueryState BdEngineBackend::execUpsertDBAction(const DbEngineAction& action, const QVariant& id, const QStringList fieldNames, const QList& values) { QMap parameters; QMap fieldValueMap; for (int i = 0 ; i < fieldNames.size() ; ++i) { fieldValueMap.insert(fieldNames.at(i), values.at(i)); } DbEngineActionType fieldValueList = DbEngineActionType::value(fieldValueMap); DbEngineActionType fieldList = DbEngineActionType::fieldEntry(fieldNames); DbEngineActionType valueList = DbEngineActionType::value(values); parameters.insert(QLatin1String(":id"), id); parameters.insert(QLatin1String(":fieldValueList"), qVariantFromValue(fieldValueList)); parameters.insert(QLatin1String(":fieldList"), qVariantFromValue(fieldList)); parameters.insert(QLatin1String(":valueList"), qVariantFromValue(valueList)); return execDBAction(action, parameters); } BdEngineBackend::QueryState BdEngineBackend::execUpsertDBAction(const QString& action, const QVariant& id, const QStringList fieldNames, const QList& values) { return execUpsertDBAction(getDBAction(action), id, fieldNames, values); } bool BdEngineBackend::connectionErrorHandling(int /*retries*/) { Q_D(BdEngineBackend); if (d->reconnectOnError()) { if (d->handleWithErrorHandler(nullptr)) { d->closeDatabaseForThread(); return true; } } return false; } bool BdEngineBackend::queryErrorHandling(DbEngineSqlQuery& query, int retries) { Q_D(BdEngineBackend); if (d->isSQLiteLockError(query)) { if (d->checkRetrySQLiteLockError(retries)) { return true; } } d->debugOutputFailedQuery(query); /* * Check if the error is query or database related. * It seems, that insufficient privileges results only in query errors, * the database gives an invalid lastError() value. */ if (query.lastError().isValid()) { d->setDatabaseErrorForThread(query.lastError()); } else { d->setDatabaseErrorForThread(d->databaseForThread().lastError()); } if (d->isConnectionError(query) && d->reconnectOnError()) { // after connection errors, it can be required // to start with a new connection and a fresh, copied query d->closeDatabaseForThread(); query = copyQuery(query); } if (d->needToHandleWithErrorHandler(query)) { if (d->handleWithErrorHandler(&query)) { return true; } else { return false; } } return false; } bool BdEngineBackend::transactionErrorHandling(const QSqlError& lastError, int retries) { Q_D(BdEngineBackend); if (d->isSQLiteLockTransactionError(lastError)) { if (d->checkRetrySQLiteLockError(retries)) { return true; } } d->debugOutputFailedTransaction(lastError); // no experience with other forms of failure return false; } BdEngineBackend::QueryState BdEngineBackend::execDirectSql(const QString& sql) { Q_D(BdEngineBackend); if (!d->checkOperationStatus()) { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } DbEngineSqlQuery query = getQuery(); int retries = 0; forever { if (query.exec(sql)) { break; } else { if (queryErrorHandling(query, retries++)) { continue; } else { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } } } return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); } BdEngineBackend::QueryState BdEngineBackend::execDirectSqlWithResult(const QString& sql, QList* const values, QVariant* const lastInsertId) { Q_D(BdEngineBackend); if (!d->checkOperationStatus()) { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } DbEngineSqlQuery query = getQuery(); int retries = 0; forever { if (query.exec(sql)) { handleQueryResult(query, values, lastInsertId); break; } else { if (queryErrorHandling(query, retries++)) { continue; } else { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } } } return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); } bool BdEngineBackend::exec(DbEngineSqlQuery& query) { Q_D(BdEngineBackend); if (!d->checkOperationStatus()) { return false; } int retries = 0; forever { /* qCDebug(DIGIKAM_DBENGINE_LOG) << "Trying to query [" << query.lastQuery() << "] values [" << query.boundValues() << "]"; */ if (query.exec()) // krazy:exclude=crashy { break; } else { if (queryErrorHandling(query, retries++)) { continue; } else { return false; } } } return true; } bool BdEngineBackend::execBatch(DbEngineSqlQuery& query) { Q_D(BdEngineBackend); if (!d->checkOperationStatus()) { return false; } int retries = 0; forever { if (query.execBatch()) { break; } else { if (queryErrorHandling(query, retries++)) { continue; } else { return false; } } } return true; } DbEngineSqlQuery BdEngineBackend::prepareQuery(const QString& sql) { int retries = 0; forever { DbEngineSqlQuery query = getQuery(); if (query.prepare(sql)) { return query; } else { qCDebug(DIGIKAM_DBENGINE_LOG) << "Prepare failed!"; if (queryErrorHandling(query, retries++)) { continue; } else { return query; } } } } DbEngineSqlQuery BdEngineBackend::copyQuery(const DbEngineSqlQuery& old) { DbEngineSqlQuery query = getQuery(); // qCDebug(DIGIKAM_DBENGINE_LOG) << "Last query was [" << old.lastQuery() << "]"; query.prepare(old.lastQuery()); query.setForwardOnly(old.isForwardOnly()); // only for positional binding QList boundValues = old.boundValues().values(); foreach (const QVariant& value, boundValues) { // qCDebug(DIGIKAM_DBENGINE_LOG) << "Bind value to query ["<databaseForThread(); DbEngineSqlQuery query(db); query.setForwardOnly(true); return query; } BdEngineBackend::QueryState BdEngineBackend::beginTransaction() { Q_D(BdEngineBackend); // Call databaseForThread before touching transaction count - open() will reset the count! QSqlDatabase db = d->databaseForThread(); if (d->incrementTransactionCount()) { int retries = 0; forever { if (db.transaction()) { break; } else { if (transactionErrorHandling(db.lastError(), retries++)) { continue; } else { d->decrementTransactionCount(); if (db.lastError().type() == QSqlError::ConnectionError) { return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); } else { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } } } } d->isInTransaction = true; } return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); } BdEngineBackend::QueryState BdEngineBackend::commitTransaction() { Q_D(BdEngineBackend); if (d->decrementTransactionCount()) { QSqlDatabase db = d->databaseForThread(); int retries = 0; forever { if (db.commit()) { break; } else { QSqlError lastError = db.lastError(); if (transactionErrorHandling(lastError, retries++)) { continue; } else { qCDebug(DIGIKAM_DBENGINE_LOG) << "Failed to commit transaction. Starting rollback."; db.rollback(); if (lastError.type() == QSqlError::ConnectionError) { return BdEngineBackend::QueryState(BdEngineBackend::ConnectionError); } else { return BdEngineBackend::QueryState(BdEngineBackend::SQLError); } } } } d->isInTransaction = false; d->transactionFinished(); } return BdEngineBackend::QueryState(BdEngineBackend::NoErrors); } bool BdEngineBackend::isInTransaction() const { Q_D(const BdEngineBackend); return d->isInTransaction; } void BdEngineBackend::rollbackTransaction() { Q_D(BdEngineBackend); // we leave that out for transaction counting. It's an exceptional condition. d->databaseForThread().rollback(); } QStringList BdEngineBackend::tables() { Q_D(BdEngineBackend); return d->databaseForThread().tables(); } QSqlError BdEngineBackend::lastSQLError() { Q_D(BdEngineBackend); return d->databaseErrorForThread(); } QString BdEngineBackend::lastError() { Q_D(BdEngineBackend); return d->databaseForThread().lastError().text(); } int BdEngineBackend::maximumBoundValues() const { Q_D(const BdEngineBackend); if (d->parameters.isSQLite()) { return 999; // SQLITE_MAX_VARIABLE_NUMBER } else { return 65535; // MySQL } } void BdEngineBackend::setForeignKeyChecks(bool check) { Q_D(BdEngineBackend); if (d->parameters.isMySQL()) { if (check) { execSql(QLatin1String("SET FOREIGN_KEY_CHECKS=1;")); } else { execSql(QLatin1String("SET FOREIGN_KEY_CHECKS=0;")); } } } } // namespace Digikam diff --git a/core/libs/database/engine/dbengineconfigloader.cpp b/core/libs/database/engine/dbengineconfigloader.cpp index 336250900a..a235c48602 100644 --- a/core/libs/database/engine/dbengineconfigloader.cpp +++ b/core/libs/database/engine/dbengineconfigloader.cpp @@ -1,285 +1,285 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-06-27 * Description : Database Engine element configuration loader * * Copyright (C) 2009-2010 by Holger Foerster * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "dbengineconfigloader.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { DbEngineConfigSettingsLoader::DbEngineConfigSettingsLoader(const QString& filepath, int xmlVersion) { isValid = readConfig(filepath, xmlVersion); if (!isValid) { qCDebug(DIGIKAM_DBENGINE_LOG) << errorMessage; } } DbEngineConfigSettings DbEngineConfigSettingsLoader::readDatabase(QDomElement& databaseElement) { DbEngineConfigSettings configElement; configElement.databaseID = QLatin1String("Unidentified"); (void)configElement.databaseID; // prevent cppcheck warning. if (!databaseElement.hasAttribute(QLatin1String("name"))) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing statement attribute ."; } configElement.databaseID = databaseElement.attribute(QLatin1String("name")); QDomElement element = databaseElement.namedItem(QLatin1String("databaseName")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.databaseName = element.text(); element = databaseElement.namedItem(QLatin1String("userName")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.userName = element.text(); element = databaseElement.namedItem(QLatin1String("password")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.password = element.text(); element = databaseElement.namedItem(QLatin1String("hostName")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.hostName = element.text(); element = databaseElement.namedItem(QLatin1String("port")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.port = element.text(); element = databaseElement.namedItem(QLatin1String("connectoptions")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } configElement.connectOptions = element.text(); element = databaseElement.namedItem(QLatin1String("dbactions")).toElement(); if (element.isNull()) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing element ."; } readDBActions(element, configElement); return configElement; } void DbEngineConfigSettingsLoader::readDBActions(QDomElement& sqlStatementElements, DbEngineConfigSettings& configElement) { QDomElement dbActionElement = sqlStatementElements.firstChildElement(QLatin1String("dbaction")); for ( ; !dbActionElement.isNull(); dbActionElement=dbActionElement.nextSiblingElement(QLatin1String("dbaction"))) { if (!dbActionElement.hasAttribute(QLatin1String("name"))) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing statement attribute ."; } DbEngineAction action; action.name = dbActionElement.attribute(QLatin1String("name")); //qCDebug(DIGIKAM_DBENGINE_LOG) << "Getting attribute " << dbActionElement.attribute("name"); if (dbActionElement.hasAttribute(QLatin1String("mode"))) { action.mode = dbActionElement.attribute(QLatin1String("mode")); } QDomElement databaseElement = dbActionElement.firstChildElement(QLatin1String("statement")); for ( ; !databaseElement.isNull(); databaseElement = databaseElement.nextSiblingElement(QLatin1String("statement"))) { if (!databaseElement.hasAttribute(QLatin1String("mode"))) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Missing statement attribute ."; } DbEngineActionElement actionElement; actionElement.mode = databaseElement.attribute(QLatin1String("mode")); actionElement.statement = databaseElement.text(); action.dbActionElements.append(actionElement); } configElement.sqlStatements.insert(action.name, action); } } bool DbEngineConfigSettingsLoader::readConfig(const QString& filepath, int xmlVersion) { qCDebug(DIGIKAM_DBENGINE_LOG) << "Loading SQL code from config file" << filepath; QFile file(filepath); if (!file.exists()) { errorMessage = i18n("Could not open the configuration file %1. " "This file is installed with the digikam application " "and is absolutely required to run digikam. " "Please check your installation.", filepath); return false; } if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { errorMessage = i18n("Could not open configuration file %1", filepath); return false; } QDomDocument doc(QLatin1String("DBConfig")); if (!doc.setContent(&file)) { errorMessage = i18n("The XML in the configuration file %1 is invalid and cannot be read.", filepath); file.close(); return false; } file.close(); QDomElement element = doc.namedItem(QLatin1String("databaseconfig")).toElement(); if (element.isNull()) { errorMessage = i18n("The XML in the configuration file %1 " "is missing the required element %2", filepath, element.tagName()); return false; } QDomElement defaultDB = element.namedItem(QLatin1String("defaultDB")).toElement(); if (defaultDB.isNull()) { errorMessage = i18n("The XML in the configuration file %1 " "is missing the required element %2", filepath, element.tagName()); return false; } QDomElement versionElement = element.namedItem(QLatin1String("version")).toElement(); int version = 0; qCDebug(DIGIKAM_DBENGINE_LOG) << "Checking XML version ID => expected: " << xmlVersion << " found: " << versionElement.text().toInt(); if (!versionElement.isNull()) { version = versionElement.text().toInt(); } if (version < xmlVersion) { errorMessage = i18n("An old version of the configuration file %1 " "is found. Please ensure that the version released " "with the running version of digiKam is installed. ", filepath); return false; } //qCDebug(DIGIKAM_DBENGINE_LOG) << "Default DB Node contains: " << defaultDB.text(); QDomElement databaseElement = element.firstChildElement(QLatin1String("database")); for ( ; !databaseElement.isNull(); databaseElement=databaseElement.nextSiblingElement(QLatin1String("database"))) { DbEngineConfigSettings l_DBCfgElement = readDatabase(databaseElement); databaseConfigs.insert(l_DBCfgElement.databaseID, l_DBCfgElement); } /* qCDebug(DIGIKAM_DBENGINE_LOG) << "Found entries: " << databaseConfigs.size(); - foreach(const DbEngineConfigSettings& configElement, databaseConfigs ) + foreach (const DbEngineConfigSettings& configElement, databaseConfigs ) { qCDebug(DIGIKAM_DBENGINE_LOG) << "DatabaseID: " << configElement.databaseID; qCDebug(DIGIKAM_DBENGINE_LOG) << "HostName: " << configElement.hostName; qCDebug(DIGIKAM_DBENGINE_LOG) << "DatabaseName: " << configElement.databaseName; qCDebug(DIGIKAM_DBENGINE_LOG) << "UserName: " << configElement.userName; qCDebug(DIGIKAM_DBENGINE_LOG) << "Password: " << configElement.password; qCDebug(DIGIKAM_DBENGINE_LOG) << "Port: " << configElement.port; qCDebug(DIGIKAM_DBENGINE_LOG) << "ConnectOptions: " << configElement.connectOptions; qCDebug(DIGIKAM_DBENGINE_LOG) << "Database Server CMD: " << configElement.dbServerCmd; qCDebug(DIGIKAM_DBENGINE_LOG) << "Database Init CMD: " << configElement.dbInitCmd; qCDebug(DIGIKAM_DBENGINE_LOG) << "Statements:"; - foreach(const QString& actionKey, configElement.sqlStatements.keys()) + foreach (const QString& actionKey, configElement.sqlStatements.keys()) { QList l_DBActionElement = configElement.sqlStatements[actionKey].dBActionElements; qCDebug(DIGIKAM_DBENGINE_LOG) << "DBAction [" << actionKey << "] has [" << l_DBActionElement.size() << "] actions"; - foreach(const databaseActionElement statement, l_DBActionElement) + foreach (const databaseActionElement statement, l_DBActionElement) { qCDebug(DIGIKAM_DBENGINE_LOG) << "\tMode ["<< statement.mode <<"] Value ["<< statement.statement <<"]"; } } } */ return true; } } // namespace Digikam diff --git a/core/libs/database/history/itemhistorygraphmodel.cpp b/core/libs/database/history/itemhistorygraphmodel.cpp index 2fdb05ddc2..29477a0702 100644 --- a/core/libs/database/history/itemhistorygraphmodel.cpp +++ b/core/libs/database/history/itemhistorygraphmodel.cpp @@ -1,1004 +1,1004 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-10-27 * Description : Model to an ItemHistoryGraph * * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "itemhistorygraphmodel.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "dcategorizedsortfilterproxymodel.h" #include "dimgfiltermanager.h" #include "itemlistmodel.h" #include "itemhistorygraphdata.h" namespace Digikam { class Q_DECL_HIDDEN HistoryTreeItem { public: enum HistoryTreeItemType { UnspecifiedType, VertexItemType, FilterActionItemType, HeaderItemType, CategoryItemType, SeparatorItemType }; public: explicit HistoryTreeItem(); virtual ~HistoryTreeItem(); virtual HistoryTreeItemType type() const { return UnspecifiedType; } bool isType(HistoryTreeItemType t) const { return (type() == t); } void addItem(HistoryTreeItem* child); int childCount() const { return children.size(); } HistoryTreeItem* child(int index) const { return children.at(index); } public: HistoryTreeItem* parent; QList children; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN VertexItem : public HistoryTreeItem { public: VertexItem() { } explicit VertexItem(const HistoryGraph::Vertex& v) : vertex(v), category(HistoryImageId::InvalidType) { } HistoryTreeItemType type() const override { return VertexItemType; } public: HistoryGraph::Vertex vertex; QModelIndex index; HistoryImageId::Types category; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN FilterActionItem : public HistoryTreeItem { public: FilterActionItem() { } explicit FilterActionItem(const FilterAction& action) : action(action) { } HistoryTreeItemType type() const override { return FilterActionItemType; } public: FilterAction action; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN HeaderItem : public HistoryTreeItem { public: explicit HeaderItem(const QString& title) : title(title) { } HistoryTreeItemType type() const override { return HeaderItemType; } public: QString title; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN CategoryItem : public HistoryTreeItem { public: explicit CategoryItem(const QString& title) : title(title) { } HistoryTreeItemType type() const override { return CategoryItemType; } public: QString title; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN SeparatorItem : public HistoryTreeItem { public: HistoryTreeItemType type() const override { return SeparatorItemType; } }; // ------------------------------------------------------------------------ #define if_isItem(class, name, pointer) \ if (pointer && static_cast(pointer)->type() == HistoryTreeItem:: class##Type) \ for (class* name = static_cast(pointer) ; name ; name=0) // ------------------------------------------------------------------------ HistoryTreeItem::HistoryTreeItem() : parent(nullptr) { } HistoryTreeItem::~HistoryTreeItem() { qDeleteAll(children); } void HistoryTreeItem::addItem(HistoryTreeItem* child) { children << child; child->parent = this; } // ------------------------------------------------------------------------ static bool oldestInfoFirst(const ItemInfo&a, const ItemInfo& b) { return a.modDateTime() < b.modDateTime(); } static bool newestInfoFirst(const ItemInfo&a, const ItemInfo& b) { return a.modDateTime() > b.modDateTime(); } template class Q_DECL_HIDDEN LessThanOnVertexItemInfo { public: LessThanOnVertexItemInfo(const HistoryGraph& graph, ItemInfoLessThan imageInfoLessThan) : graph(graph), imageInfoLessThan(imageInfoLessThan) { } bool operator()(const HistoryGraph::Vertex& a, const HistoryGraph::Vertex& b) const { const HistoryVertexProperties& propsA = graph.properties(a); const HistoryVertexProperties& propsB = graph.properties(b); if (propsA.infos.isEmpty()) return false; else if (propsB.infos.isEmpty()) return true; return imageInfoLessThan(propsA.infos.first(), propsB.infos.first()); } public: const HistoryGraph& graph; ItemInfoLessThan imageInfoLessThan; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN ItemHistoryGraphModel::Private { public: explicit Private() : mode(ItemHistoryGraphModel::CombinedTreeMode), rootItem(nullptr) { } ItemHistoryGraphModel::Mode mode; ItemHistoryGraph historyGraph; ItemInfo info; HistoryTreeItem* rootItem; QList vertexItems; ItemListModel imageModel; QList path; QHash categories; public: inline const ItemHistoryGraphData& graph() const { return historyGraph.data(); } inline HistoryTreeItem* historyItem(const QModelIndex& index) const { return index.isValid() ? static_cast(index.internalPointer()) : rootItem; } void build(); void buildImagesList(); void buildImagesTree(); void buildCombinedTree(const HistoryGraph::Vertex& ref); void addCombinedItemCategory(HistoryTreeItem* parentItem, QList& vertices, const QString& title, const HistoryGraph::Vertex& showActionsFrom, QList& added); void addItemSubgroup(VertexItem* parent, const QList& vertices, const QString& title, bool flat = false); void addIdenticalItems(HistoryTreeItem* parentItem, const HistoryGraph::Vertex& vertex, const QList& infos, const QString& title); VertexItem* createVertexItem(const HistoryGraph::Vertex& v, const ItemInfo& info = ItemInfo()); FilterActionItem* createFilterActionItem(const FilterAction& action); template LessThanOnVertexItemInfo sortBy(ItemInfoLessThan imageInfoLessThan) { return LessThanOnVertexItemInfo(graph(), imageInfoLessThan); } }; // ------------------------------------------------------------------------ VertexItem* ItemHistoryGraphModel::Private::createVertexItem(const HistoryGraph::Vertex& v, const ItemInfo& givenInfo) { const HistoryVertexProperties& props = graph().properties(v); ItemInfo info = givenInfo.isNull() ? props.firstItemInfo() : givenInfo; QModelIndex index = imageModel.indexForItemInfo(info); //qCDebug(DIGIKAM_DATABASE_LOG) << "Added" << info.id() << index; VertexItem* item = new VertexItem(v); item->index = index; item->category = categories.value(v); vertexItems << item; //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex item" << graph().properties(v).firstItemInfo().id() << index; return item; } FilterActionItem* ItemHistoryGraphModel::Private::createFilterActionItem(const FilterAction& action) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Adding vertex item for" << action.displayableName(); return new FilterActionItem(action); } void ItemHistoryGraphModel::Private::build() { delete rootItem; vertexItems.clear(); rootItem = new HistoryTreeItem; //qCDebug(DIGIKAM_DATABASE_LOG) << historyGraph; HistoryGraph::Vertex ref = graph().findVertexByProperties(info); path = graph().longestPathTouching(ref, sortBy(newestInfoFirst)); categories = graph().categorize(); if (path.isEmpty()) return; if (mode == ItemHistoryGraphModel::ImagesListMode) { buildImagesList(); } else if (mode == ItemHistoryGraphModel::ImagesTreeMode) { buildImagesTree(); } else if (mode == CombinedTreeMode) { buildCombinedTree(ref); } } void ItemHistoryGraphModel::Private::buildImagesList() { QList verticesOrdered = graph().verticesDepthFirstSorted(path.first(), sortBy(oldestInfoFirst)); - foreach(const HistoryGraph::Vertex& v, verticesOrdered) + foreach (const HistoryGraph::Vertex& v, verticesOrdered) { rootItem->addItem(createVertexItem(v)); } } void ItemHistoryGraphModel::Private::buildImagesTree() { QList verticesOrdered = graph().verticesDepthFirstSorted(path.first(), sortBy(oldestInfoFirst)); QMap distances = graph().shortestDistancesFrom(path.first()); QList sources; int previousLevel = 0; HistoryTreeItem* parent = rootItem; VertexItem* item = nullptr; VertexItem* previousItem = nullptr; - foreach(const HistoryGraph::Vertex& v, verticesOrdered) + foreach (const HistoryGraph::Vertex& v, verticesOrdered) { int currentLevel = distances.value(v); if (currentLevel == -1) { // unreachable from first root if (graph().isRoot(v) && parent == rootItem) { // other first-level root? parent->addItem(createVertexItem(v)); } else { // add later as sources sources << v; } continue; } item = createVertexItem(v); if (!sources.isEmpty()) { addItemSubgroup(item, sources, i18nc("@title", "Source Images")); } if (currentLevel == previousLevel) { parent->addItem(item); } else if (currentLevel > previousLevel && previousItem) // check pointer, prevent crash is distances are faulty { previousItem->addItem(item); parent = previousItem; } else if (currentLevel < previousLevel) { for (int level = currentLevel; level < previousLevel; ++level) { parent = parent->parent; } parent->addItem(item); } previousItem = item; previousLevel = currentLevel; } } void ItemHistoryGraphModel::Private::buildCombinedTree(const HistoryGraph::Vertex& ref) { VertexItem* item = nullptr; CategoryItem *categoryItem = new CategoryItem(i18nc("@title", "Image History")); rootItem->addItem(categoryItem); QList added; QList currentVersions = categories.keys(HistoryImageId::Current); QList leavesFromRef = graph().leavesFrom(ref); bool onePath = (leavesFromRef.size() <= 1); for (int i = 0 ; i < path.size() ; ++i) { const HistoryGraph::Vertex& v = path.at(i); HistoryGraph::Vertex previous = i ? path.at(i-1) : HistoryGraph::Vertex(); // HistoryGraph::Vertex next = i < path.size() - 1 ? path[i+1] : HistoryGraph::Vertex(); // qCDebug(DIGIKAM_DATABASE_LOG) << "Vertex on path" << path[i]; // create new item item = createVertexItem(v); QList vertices; // any extra sources? QList sources = graph().adjacentVertices(item->vertex, HistoryGraph::EdgesToRoot); - foreach(const HistoryGraph::Vertex& source, sources) + foreach (const HistoryGraph::Vertex& source, sources) { if (source != previous) { rootItem->addItem(createVertexItem(source)); } } /* // Any other egdes off the main path? QList branches = graph().adjacentVertices(v, HistoryGraph::EdgesToLeaf); QList subgraph; - foreach(const HistoryGraph::Vertex& branch, branches) + foreach (const HistoryGraph::Vertex& branch, branches) { if (branch != next) { subgraph << graph().verticesDominatedByDepthFirstSorted(branch, v, sortBy(oldestInfoFirst)); } } addItemSubgroup(item, subgraph, i18nc("@title", "More Derived Images")); */ // Add filter actions above item HistoryEdgeProperties props = graph().properties(v, previous); - foreach(const FilterAction& action, props.actions) + foreach (const FilterAction& action, props.actions) { rootItem->addItem(createFilterActionItem(action)); } // now, add item rootItem->addItem(item); added << v; // If there are multiple derived images, we display them in the next section if (v == ref && !onePath) break; } - foreach(const HistoryGraph::Vertex& v, added) + foreach (const HistoryGraph::Vertex& v, added) { leavesFromRef.removeOne(v); } if (!leavesFromRef.isEmpty()) { addCombinedItemCategory(rootItem, leavesFromRef, i18nc("@title", "Derived Images"), ref, added); } - foreach(const HistoryGraph::Vertex& v, added) + foreach (const HistoryGraph::Vertex& v, added) { currentVersions.removeOne(v); } if (!currentVersions.isEmpty()) { addCombinedItemCategory(rootItem, currentVersions, i18nc("@title", "Related Images"), path.first(), added); } QList allInfos = graph().properties(ref).infos; if (allInfos.size() > 1) { addIdenticalItems(rootItem, ref, allInfos, i18nc("@title", "Identical Images")); } } void ItemHistoryGraphModel::Private::addCombinedItemCategory(HistoryTreeItem* parentItem, QList& vertices, const QString& title, const HistoryGraph::Vertex& showActionsFrom, QList& added) { parentItem->addItem(new CategoryItem(title)); std::sort(vertices.begin(), vertices.end(), sortBy(oldestInfoFirst)); bool isFirst = true; VertexItem* item = nullptr; - foreach(const HistoryGraph::Vertex& v, vertices) + foreach (const HistoryGraph::Vertex& v, vertices) { if (isFirst) { isFirst = false; } else { parentItem->addItem(new SeparatorItem); } item = createVertexItem(v); QList shortestPath = graph().shortestPath(showActionsFrom, v); // add all filter actions showActionsFrom -> v above item for (int i = 1 ; i < shortestPath.size() ; ++i) { HistoryEdgeProperties props = graph().properties(shortestPath.at(i), shortestPath.at(i-1)); - foreach(const FilterAction& action, props.actions) + foreach (const FilterAction& action, props.actions) { parentItem->addItem(createFilterActionItem(action)); } } parentItem->addItem(item); added << v; // Provide access to intermediates shortestPath.removeOne(showActionsFrom); shortestPath.removeOne(v); - foreach(const HistoryGraph::Vertex& addedVertex, added) + foreach (const HistoryGraph::Vertex& addedVertex, added) { shortestPath.removeOne(addedVertex); } addItemSubgroup(item, shortestPath, i18nc("@title", "Intermediate Steps:"), true); } } void ItemHistoryGraphModel::Private::addItemSubgroup(VertexItem* parent, const QList& vertices, const QString& title, bool flat) { if (vertices.isEmpty()) return; HeaderItem* const header = new HeaderItem(title); parent->addItem(header); HistoryTreeItem* const addToItem = flat ? static_cast(parent) : static_cast(header); - foreach(const HistoryGraph::Vertex& v, vertices) + foreach (const HistoryGraph::Vertex& v, vertices) { addToItem->addItem(createVertexItem(v)); } } void ItemHistoryGraphModel::Private::addIdenticalItems(HistoryTreeItem* parentItem, const HistoryGraph::Vertex& vertex, const QList& infos, const QString& title) { parentItem->addItem(new CategoryItem(title)); // the properties image info list is already sorted by proximity to subject VertexItem* item = nullptr; bool isFirst = true; for (int i = 1 ; i < infos.size() ; ++i) { if (isFirst) { isFirst = false; } else { parentItem->addItem(new SeparatorItem); } item = createVertexItem(vertex, infos.at(i)); parentItem->addItem(item); } } // ------------------------------------------------------------------------ ItemHistoryGraphModel::ItemHistoryGraphModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->rootItem = new HistoryTreeItem; } ItemHistoryGraphModel::~ItemHistoryGraphModel() { delete d->rootItem; delete d; } void ItemHistoryGraphModel::setMode(Mode mode) { if (d->mode == mode) return; d->mode = mode; setHistory(d->info, d->historyGraph); } ItemHistoryGraphModel::Mode ItemHistoryGraphModel::mode() const { return d->mode; } void ItemHistoryGraphModel::setHistory(const ItemInfo& subject, const ItemHistoryGraph& graph) { beginResetModel(); d->info = subject; if (graph.isNull()) { d->historyGraph = ItemHistoryGraph::fromInfo(subject); } else { d->historyGraph = graph; d->historyGraph.prepareForDisplay(subject); } // fill helper model d->imageModel.clearItemInfos(); d->imageModel.addItemInfos(d->historyGraph.allImages()); d->build(); endResetModel(); } ItemInfo ItemHistoryGraphModel::subject() const { return d->info; } bool ItemHistoryGraphModel::isImage(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); return (item && item->isType(HistoryTreeItem::VertexItemType)); } bool ItemHistoryGraphModel::isFilterAction(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); return (item && item->isType(HistoryTreeItem::FilterActionItemType)); } FilterAction ItemHistoryGraphModel::filterAction(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); if_isItem(FilterActionItem, filterActionItem, item) { return filterActionItem->action; } return FilterAction(); } bool ItemHistoryGraphModel::hasImage(const ItemInfo& info) { return d->imageModel.hasImage(info); } ItemInfo ItemHistoryGraphModel::imageInfo(const QModelIndex& index) const { QModelIndex imageIndex = imageModelIndex(index); return ItemModel::retrieveItemInfo(imageIndex); } QModelIndex ItemHistoryGraphModel::indexForInfo(const ItemInfo& info) const { if (info.isNull()) { return QModelIndex(); } // try with primary info - foreach(VertexItem* const item, d->vertexItems) + foreach (VertexItem* const item, d->vertexItems) { if (ItemModel::retrieveItemInfo(item->index) == info) { return createIndex(item->parent->children.indexOf(item), 0, item); } } // try all associated infos - foreach(VertexItem* const item, d->vertexItems) + foreach (VertexItem* const item, d->vertexItems) { if (d->graph().properties(item->vertex).infos.contains(info)) { return createIndex(item->parent->children.indexOf(item), 0, item); } } return QModelIndex(); } QVariant ItemHistoryGraphModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { if (vertexItem->index.isValid()) { QVariant data = vertexItem->index.data(role); switch (role) { case IsImageItemRole: { return true; } case IsSubjectImageRole: { return (bool)d->graph().properties(vertexItem->vertex).infos.contains(d->info); } case Qt::DisplayRole: { if (vertexItem->category & HistoryImageId::Original) { return i18nc("@item filename", "%1\n(Original Image)", data.toString()); } if (vertexItem->category & HistoryImageId::Source) { return i18nc("@item filename", "%1\n(Source Image)", data.toString()); } break; } } return data; } // else: read HistoryImageId from d->graph().properties(vertexItem->vertex)? } else if_isItem(FilterActionItem, filterActionItem, item) { switch (role) { case IsFilterActionItemRole: { return true; } case Qt::DisplayRole: { return DImgFilterManager::instance()->i18nDisplayableName(filterActionItem->action); } case Qt::DecorationRole: { QString iconName = DImgFilterManager::instance()->filterIcon(filterActionItem->action); return QIcon::fromTheme(iconName); } case FilterActionRole: { return QVariant::fromValue(filterActionItem->action); } default: { break; } } } else if_isItem(HeaderItem, headerItem, item) { switch (role) { case IsHeaderItemRole: return true; case Qt::DisplayRole: //case Qt::ToolTipRole: return headerItem->title; break; } } else if_isItem(CategoryItem, categoryItem, item) { switch (role) { case IsCategoryItemRole: return true; case Qt::DisplayRole: case DCategorizedSortFilterProxyModel::CategoryDisplayRole: //case Qt::ToolTipRole: return categoryItem->title; } } else if_isItem(SeparatorItem, separatorItem, item) { switch (role) { case IsSeparatorItemRole: return true; } } switch (role) { case IsImageItemRole: case IsFilterActionItemRole: case IsHeaderItemRole: case IsCategoryItemRole: case IsSubjectImageRole: return false; default: return QVariant(); } } bool ItemHistoryGraphModel::setData(const QModelIndex& index, const QVariant& value, int role) { HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { if (vertexItem->index.isValid()) { return d->imageModel.setData(vertexItem->index, value, role); } } return false; } ItemListModel* ItemHistoryGraphModel::imageModel() const { return &d->imageModel; } QModelIndex ItemHistoryGraphModel::imageModelIndex(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { return vertexItem->index; } return QModelIndex(); } QVariant ItemHistoryGraphModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section) Q_UNUSED(orientation) Q_UNUSED(role) return QVariant(); } int ItemHistoryGraphModel::rowCount(const QModelIndex& parent) const { return d->historyItem(parent)->childCount(); } int ItemHistoryGraphModel::columnCount(const QModelIndex&) const { return 1; } Qt::ItemFlags ItemHistoryGraphModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } HistoryTreeItem* const item = d->historyItem(index); if_isItem(VertexItem, vertexItem, item) { return d->imageModel.flags(vertexItem->index); } if (item) { switch (item->type()) { case HistoryTreeItem::FilterActionItemType: return Qt::ItemIsEnabled | Qt::ItemIsSelectable; case HistoryTreeItem::HeaderItemType: case HistoryTreeItem::CategoryItemType: case HistoryTreeItem::SeparatorItemType: default: return Qt::ItemIsEnabled; } } return Qt::ItemIsEnabled; } QModelIndex ItemHistoryGraphModel::index(int row, int column , const QModelIndex& parent) const { if (column != 0 || row < 0) { return QModelIndex(); } HistoryTreeItem* const item = d->historyItem(parent); if (row >= item->childCount()) { return QModelIndex(); } return createIndex(row, 0, item->child(row)); } bool ItemHistoryGraphModel::hasChildren(const QModelIndex& parent) const { return d->historyItem(parent)->childCount(); } QModelIndex ItemHistoryGraphModel::parent(const QModelIndex& index) const { HistoryTreeItem* const item = d->historyItem(index); HistoryTreeItem* const parent = item->parent; if (!parent) { return QModelIndex(); // index was an invalid index } HistoryTreeItem* const grandparent = parent->parent; if (!grandparent) { return QModelIndex(); // index was a top-level index, was the invisible rootItem as parent } return createIndex(grandparent->children.indexOf(parent), 0, parent); } } // namespace Digikam diff --git a/core/libs/database/item/containers/itemcomments.cpp b/core/libs/database/item/containers/itemcomments.cpp index bbbe8c4054..de2fd519cd 100644 --- a/core/libs/database/item/containers/itemcomments.cpp +++ b/core/libs/database/item/containers/itemcomments.cpp @@ -1,693 +1,693 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-09-19 * Description : Access to comments of an item in the database * * Copyright (C) 2007-2013 by Marcel Wiesweg * Copyright (C) 2009-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "itemcomments.h" // Qt includes #include // Local includes #include "coredb.h" namespace Digikam { class Q_DECL_HIDDEN ItemComments::Private : public QSharedData { public: explicit Private() : id(-1), unique(ItemComments::UniquePerLanguage) { } void init(CoreDbAccess& access, qlonglong imageId) { id = imageId; infos = access.db()->getItemComments(id); for (int i = 0 ; i < infos.size() ; ++i) { CommentInfo& info = infos[i]; if (info.language.isNull()) { info.language = QLatin1String("x-default"); } } } void languageMatch(const QString& fullCode, const QString& langCode, int& fullCodeMatch, int& langCodeMatch, int& defaultCodeMatch, int& firstMatch, DatabaseComment::Type type = DatabaseComment::Comment) const { // if you change the algorithm, please take a look at ItemCopyright as well fullCodeMatch = -1; langCodeMatch = -1; defaultCodeMatch = -1; firstMatch = -1; if (infos.isEmpty()) { return; } // First we search for a full match // Second for a match of the language code // Third for the default code // Fourth we return the first comment QLatin1String defaultCode("x-default"); for (int i = 0 ; i < infos.size() ; ++i) { const CommentInfo& info = infos.at(i); if (info.type == type) { if (firstMatch == -1) { firstMatch = i; } if (info.language == fullCode) { fullCodeMatch = i; break; } else if (info.language.startsWith(langCode) && langCodeMatch == -1) { langCodeMatch = i; } else if (info.language == defaultCode) { defaultCodeMatch = i; } } } } void adjustStoredIndexes(QSet &set, int removedIndex) { QSet newSet; - foreach(int index, set) + foreach (int index, set) { if (index > removedIndex) { newSet << index - 1; } else if (index < removedIndex) { newSet << index; } // drop index == removedIndex } set = newSet; } void adjustStoredIndexes(int removedIndex) { adjustStoredIndexes(dirtyIndices, removedIndex); adjustStoredIndexes(newIndices, removedIndex); } public: qlonglong id; QList infos; QSet dirtyIndices; QSet newIndices; QSet idsToRemove; ItemComments::UniqueBehavior unique; }; ItemComments::ItemComments() : d(nullptr) { } ItemComments::ItemComments(qlonglong imageid) : d(new Private) { CoreDbAccess access; d->init(access, imageid); } ItemComments::ItemComments(CoreDbAccess& access, qlonglong imageid) : d(new Private) { d->init(access, imageid); } ItemComments::ItemComments(const ItemComments& other) { d = other.d; } ItemComments::~ItemComments() { apply(); } ItemComments& ItemComments::operator=(const ItemComments& other) { d = other.d; return *this; } bool ItemComments::isNull() const { return !d; } QString ItemComments::defaultComment(int* const index, DatabaseComment::Type type) const { if (!d) { return QString(); } QString spec = QLocale().name().toLower(); QString langCode = spec.left(spec.indexOf(QLatin1Char('_'))) + QLatin1Char('-'); QString fullCode = spec.replace(QLatin1Char('_'), QLatin1Char('-')); int fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch; d->languageMatch(fullCode, langCode, fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch, type); int chosen = fullCodeMatch; if (chosen == -1) { chosen = langCodeMatch; } if (chosen == -1) { chosen = defaultCodeMatch; } if (chosen == -1) { chosen = firstMatch; } if (index) { *index = chosen; } if (chosen == -1) { return QString(); } else { return d->infos.at(chosen).comment; } } QString ItemComments::commentForLanguage(const QString& languageCode, int* const index, LanguageChoiceBehavior behavior) const { if (!d) { return QString(); } int fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch; // en-us => en- QString firstPart; if (languageCode == QLatin1String("x-default")) { firstPart = languageCode; } else { firstPart = languageCode.section(QLatin1Char('-'), 0, 0, QString::SectionIncludeTrailingSep); } d->languageMatch(languageCode, firstPart, fullCodeMatch, langCodeMatch, defaultCodeMatch, firstMatch); int chosen = fullCodeMatch; if (chosen == -1) { chosen = langCodeMatch; } if (chosen == -1 && behavior > ReturnMatchingLanguageOnly) { chosen = defaultCodeMatch; if (chosen == -1 && behavior == ReturnMatchingDefaultOrFirstLanguage) { chosen = firstMatch; } } if (index) { *index = chosen; } if (chosen == -1) { return QString(); } else { return d->infos.at(chosen).comment; } } int ItemComments::numberOfComments() const { if (!d) { return 0; } return d->infos.size(); } DatabaseComment::Type ItemComments::type(int index) const { if (!d) { return DatabaseComment::UndefinedType; } return d->infos.at(index).type; } QString ItemComments::language(int index) const { if (!d) { return QString(); } return d->infos.at(index).language; } QString ItemComments::author(int index) const { if (!d) { return QString(); } return d->infos.at(index).author; } QDateTime ItemComments::date(int index) const { if (!d) { return QDateTime(); } return d->infos.at(index).date; } QString ItemComments::comment(int index) const { if (!d) { return QString(); } return d->infos.at(index).comment; } void ItemComments::setUniqueBehavior(UniqueBehavior behavior) { if (!d) { return; } d->unique = behavior; } void ItemComments::addComment(const QString& comment, const QString& lang, const QString& author_, const QDateTime& date, DatabaseComment::Type type) { if (!d) { return; } bool multipleCommentsPerLanguage = (d->unique == UniquePerLanguageAndAuthor); QString language = lang; if (language.isEmpty()) { language = QLatin1String("x-default"); } QString author = author_; /// @todo This makes no sense - is another variable supposed to be used instead? - Michael Hansen if (author.isEmpty()) { author = QString(); } for (int i = 0 ; i < d->infos.size() ; ++i) { CommentInfo& info = d->infos[i]; // some extra considerations on replacing if (info.type == type && info.type == DatabaseComment::Comment && info.language == language) { if (!multipleCommentsPerLanguage || (info.author == author)) { info.comment = comment; info.date = date; info.author = author; d->dirtyIndices << i; return; } } // simulate unique restrictions of db. // There is a problem that a NULL value is never unique, see #189080 if ((info.type == type) && (info.language == language) && ((info.author == author) || (info.author.isEmpty() && author.isEmpty()))) { info.comment = comment; info.date = date; d->dirtyIndices << i; return; } } return addCommentDirectly(comment, language, author, type, date); } void ItemComments::addHeadline(const QString& headline, const QString& lang, const QString& author, const QDateTime& date) { return addComment(headline, lang, author, date, DatabaseComment::Headline); } void ItemComments::addTitle(const QString& title, const QString& lang, const QString& author, const QDateTime& date) { return addComment(title, lang, author, date, DatabaseComment::Title); } void ItemComments::replaceComments(const CaptionsMap& map, DatabaseComment::Type type) { if (!d) { return; } d->dirtyIndices.clear(); for (CaptionsMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { CaptionValues val = it.value(); addComment(val.caption, it.key(), val.author, val.date, type); } // remove all comments of this type that have not been touched above for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) { if (!d->dirtyIndices.contains(i) && !d->newIndices.contains(i) && d->infos[i].type == type) { remove(i); } else { ++i; } } } void ItemComments::replaceFrom(const ItemComments& source) { if (!d) { return; } if (!source.d) { removeAll(); return; } - foreach(const CommentInfo& info, source.d->infos) + foreach (const CommentInfo& info, source.d->infos) { addComment(info.comment, info.language, info.author, info.date, info.type); } // remove all that have not been touched above for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) { if (!d->dirtyIndices.contains(i) && !d->newIndices.contains(i)) { remove(i); } else { ++i; } } } void ItemComments::addCommentDirectly(const QString& comment, const QString& language, const QString& author, DatabaseComment::Type type, const QDateTime& date) { CommentInfo info; info.comment = comment; info.language = language; info.author = author; info.type = type; info.date = date; d->newIndices << d->infos.size(); d->infos << info; } void ItemComments::remove(int index) { if (!d) { return; } d->idsToRemove << d->infos.at(index).id; d->infos.removeAt(index); d->adjustStoredIndexes(index); } void ItemComments::removeAll(DatabaseComment::Type type) { if (!d) { return; } for (int i = 0 ; i < d->infos.size() /* changing! */ ; ) { if (d->infos.at(i).type == type) { remove(i); } else { ++i; } } } void ItemComments::removeAllComments() { removeAll(DatabaseComment::Comment); } void ItemComments::removeAll() { if (!d) { return; } - foreach(const CommentInfo& info, d->infos) + foreach (const CommentInfo& info, d->infos) { d->idsToRemove << info.id; } d->infos.clear(); d->dirtyIndices.clear(); d->newIndices.clear(); } void ItemComments::changeComment(int index, const QString& comment) { if (!d) { return; } d->infos[index].comment = comment; d->dirtyIndices << index; } void ItemComments::changeLanguage(int index, const QString& language) { if (!d) { return; } d->infos[index].language = language; d->dirtyIndices << index; } void ItemComments::changeAuthor(int index, const QString& author) { if (!d) { return; } d->infos[index].author = author; d->dirtyIndices << index; } void ItemComments::changeDate(int index, const QDateTime& date) { if (!d) { return; } d->infos[index].date = date; d->dirtyIndices << index; } void ItemComments::changeType(int index, DatabaseComment::Type type) { if (!d) { return; } d->infos[index].type = type; d->dirtyIndices << index; } void ItemComments::apply() { if (!d) { return; } CoreDbAccess access; apply(access); } void ItemComments::apply(CoreDbAccess& access) { if (!d) { return; } - foreach(int commentId, d->idsToRemove) + foreach (int commentId, d->idsToRemove) { access.db()->removeImageComment(commentId, d->id); } d->idsToRemove.clear(); - foreach(int index, d->newIndices) + foreach (int index, d->newIndices) { CommentInfo& info = d->infos[index]; info.id = access.db()->setImageComment(d->id, info.comment, info.type, info.language, info.author, info.date); } d->dirtyIndices.subtract(d->newIndices); d->newIndices.clear(); - foreach(int index, d->dirtyIndices) + foreach (int index, d->dirtyIndices) { QVariantList values; CommentInfo& info = d->infos[index]; values << (int)info.type << info.language << info.author << info.date << info.comment; access.db()->changeImageComment(info.id, d->id, values); } d->dirtyIndices.clear(); } CaptionsMap ItemComments::toCaptionsMap(DatabaseComment::Type type) const { CaptionsMap map; if (d) { - foreach(const CommentInfo& info, d->infos) + foreach (const CommentInfo& info, d->infos) { if (info.type == type) { CaptionValues val; val.caption = info.comment; val.author = info.author; val.date = info.date; map[info.language] = val; } } } return map; } } // namespace Digikam diff --git a/core/libs/database/item/containers/itemtagpair.cpp b/core/libs/database/item/containers/itemtagpair.cpp index 8a4adf92ea..306df32724 100644 --- a/core/libs/database/item/containers/itemtagpair.cpp +++ b/core/libs/database/item/containers/itemtagpair.cpp @@ -1,369 +1,369 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-07-05 * Description : Access to the properties of an Item / Tag pair, i.e., a tag associated to an item * * Copyright (C) 2010 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "itemtagpair.h" // Qt includes #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" #include "iteminfo.h" #include "tagscache.h" namespace Digikam { typedef QSharedDataPointer ItemTagPairPrivSharedPointer; // NOTE: do not use Q_DECL_HIDDEN hidden here to prevent export symbols warnings under Windows. class ItemTagPairPriv : public QSharedData { public: static ItemTagPairPrivSharedPointer createGuarded(qlonglong imageId, int tagId); ItemTagPairPriv() { tagId = -1; isAssigned = false; propertiesLoaded = false; } bool isNull() const; void init(const ItemInfo& info, int tagId); void checkProperties(); public: ItemInfo info; int tagId; bool isAssigned; bool propertiesLoaded; QMultiMap properties; }; // ----------------------------------------------------------------------- class Q_DECL_HIDDEN ItemTagPairPrivSharedNull : public ItemTagPairPrivSharedPointer { public: ItemTagPairPrivSharedNull() : QSharedDataPointer(new ItemTagPairPriv) { } }; Q_GLOBAL_STATIC(ItemTagPairPrivSharedNull, imageTagPairPrivSharedNull) // ----------------------------------------------------------------------- ItemTagPairPrivSharedPointer ItemTagPairPriv::createGuarded(qlonglong imageId, int tagId) { if (imageId <= 0 || tagId <= 0) { qCDebug(DIGIKAM_DATABASE_LOG) << "Attempt to create invalid tag pair image id" << imageId << "tag id" << tagId; return *imageTagPairPrivSharedNull; } return ItemTagPairPrivSharedPointer(new ItemTagPairPriv); } void ItemTagPairPriv::init(const ItemInfo& i, int t) { if (isNull()) { return; } tagId = t; info = i; isAssigned = info.tagIds().contains(tagId); } void ItemTagPairPriv::checkProperties() { if (!isNull() && !propertiesLoaded) { QList props = CoreDbAccess().db()->getImageTagProperties(info.id(), tagId); - foreach(const ImageTagProperty& p, props) + foreach (const ImageTagProperty& p, props) { properties.insert(p.property, p.value); } propertiesLoaded = true; } } bool ItemTagPairPriv::isNull() const { return this == imageTagPairPrivSharedNull->constData(); } ItemTagPair::ItemTagPair() : d(*imageTagPairPrivSharedNull) { } ItemTagPair::ItemTagPair(qlonglong imageId, int tagId) : d(ItemTagPairPriv::createGuarded(imageId, tagId)) { d->init(ItemInfo(imageId), tagId); } ItemTagPair::ItemTagPair(const ItemInfo& info, int tagId) : d(ItemTagPairPriv::createGuarded(info.id(), tagId)) { d->init(info, tagId); } ItemTagPair::~ItemTagPair() { } ItemTagPair::ItemTagPair(const ItemTagPair& other) { d = other.d; } ItemTagPair& ItemTagPair::operator=(const ItemTagPair& other) { d = other.d; return *this; } bool ItemTagPair::isNull() const { return d == *imageTagPairPrivSharedNull; } QList ItemTagPair::availablePairs(qlonglong imageId) { return availablePairs(ItemInfo(imageId)); } QList ItemTagPair::availablePairs(const ItemInfo& info) { QList pairs; if (info.isNull()) { return pairs; } QList tagIds = CoreDbAccess().db()->getTagIdsWithProperties(info.id()); - foreach(int tagId, tagIds) + foreach (int tagId, tagIds) { pairs << ItemTagPair(info, tagId); } return pairs; } qlonglong ItemTagPair::imageId() const { return d->info.id(); } int ItemTagPair::tagId() const { return d->tagId; } bool ItemTagPair::isAssigned() const { return d->isAssigned; } void ItemTagPair::assignTag() { if (!d->isNull() && !d->isAssigned) { d->info.setTag(d->tagId); d->isAssigned = true; } } void ItemTagPair::unAssignTag() { if (!d->isNull() && d->isAssigned) { d->info.removeTag(d->tagId); d->isAssigned = false; } } bool ItemTagPair::hasProperty(const QString& key) const { d->checkProperties(); return d->properties.contains(key); } bool ItemTagPair::hasAnyProperty(const QStringList& keys) const { d->checkProperties(); - foreach(const QString& key, keys) + foreach (const QString& key, keys) { if (d->properties.contains(key)) { return true; } } return false; } bool ItemTagPair::hasValue(const QString& key, const QString& value) const { d->checkProperties(); return d->properties.contains(key, value); } QString ItemTagPair::value(const QString& key) const { d->checkProperties(); return d->properties.value(key); } QStringList ItemTagPair::allValues(const QStringList& keys) const { d->checkProperties(); QStringList values; - foreach(const QString& key, keys) + foreach (const QString& key, keys) { values << d->properties.values(key); } return values; } QStringList ItemTagPair::values(const QString& key) const { d->checkProperties(); return d->properties.values(key); } QStringList ItemTagPair::propertyKeys() const { d->checkProperties(); return d->properties.keys(); } QMap ItemTagPair::properties() const { d->checkProperties(); return d->properties; } void ItemTagPair::setProperty(const QString& key, const QString& value) { if (d->isNull() || d->info.isNull()) { return; } d->checkProperties(); // for single entries in db, this can of course be optimized using a single UPDATE WHERE removeProperties(key); d->properties.replace(key, value); CoreDbAccess().db()->addImageTagProperty(d->info.id(), d->tagId, key, value); } void ItemTagPair::addProperty(const QString& key, const QString& value) { if (d->isNull() || d->info.isNull()) { return; } d->checkProperties(); if (!d->properties.contains(key, value)) { d->properties.insert(key, value); CoreDbAccess().db()->addImageTagProperty(d->info.id(), d->tagId, key, value); } } void ItemTagPair::removeProperty(const QString& key, const QString& value) { if (d->isNull() || d->info.isNull()) { return; } d->checkProperties(); if (d->properties.contains(key, value)) { CoreDbAccess().db()->removeImageTagProperties(d->info.id(), d->tagId, key, value); d->properties.remove(key, value); } } void ItemTagPair::removeProperties(const QString& key) { if (d->isNull() || d->info.isNull()) { return; } d->checkProperties(); if (d->properties.contains(key)) { CoreDbAccess().db()->removeImageTagProperties(d->info.id(), d->tagId, key); d->properties.remove(key); } } void ItemTagPair::clearProperties() { if (d->isNull() || d->info.isNull()) { return; } if (d->propertiesLoaded && d->properties.isEmpty()) { return; } CoreDbAccess().db()->removeImageTagProperties(d->info.id(), d->tagId); d->properties.clear(); d->propertiesLoaded = true; } } // namespace Digikam diff --git a/core/libs/database/models/itemfiltersettings.cpp b/core/libs/database/models/itemfiltersettings.cpp index 89c912e512..38f51bd16a 100644 --- a/core/libs/database/models/itemfiltersettings.cpp +++ b/core/libs/database/models/itemfiltersettings.cpp @@ -1,971 +1,971 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-05 * Description : Filter values for use with ItemFilterModel * * Copyright (C) 2009-2011 by Marcel Wiesweg * Copyright (C) 2011-2019 by Gilles Caulier * Copyright (C) 2010 by Andi Clemens * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2014 by Mohamed_Anwer * * 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, 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. * * ============================================================ */ #include "itemfiltersettings.h" // C++ includes #include // Qt includes #include // Local includes #include "digikam_debug.h" #include "coredbfields.h" #include "digikam_globals.h" #include "iteminfo.h" #include "tagscache.h" #include "versionmanagersettings.h" namespace Digikam { ItemFilterSettings::ItemFilterSettings() { m_untaggedFilter = false; m_isUnratedExcluded = false; m_ratingFilter = 0; m_mimeTypeFilter = MimeFilter::AllFiles; m_ratingCond = GreaterEqualCondition; m_matchingCond = OrCondition; m_geolocationCondition = GeolocationNoFilter; } DatabaseFields::Set ItemFilterSettings::watchFlags() const { DatabaseFields::Set set; if (isFilteringByDay()) { set |= DatabaseFields::CreationDate; } if (isFilteringByText()) { set |= DatabaseFields::Name; set |= DatabaseFields::Comment; } if (isFilteringByRating()) { set |= DatabaseFields::Rating; } if (isFilteringByTypeMime()) { set |= DatabaseFields::Category; set |= DatabaseFields::Format; } if (isFilteringByGeolocation()) { set |= DatabaseFields::ItemPositionsAll; } if (isFilteringByColorLabels()) { set |= DatabaseFields::ColorLabel; } if (isFilteringByPickLabels()) { set |= DatabaseFields::PickLabel; } return set; } bool ItemFilterSettings::isFilteringByDay() const { if (!m_dayFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByTags() const { if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty() || m_untaggedFilter) { return true; } return false; } bool ItemFilterSettings::isFilteringByColorLabels() const { if (!m_colorLabelTagFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByPickLabels() const { if (!m_pickLabelTagFilter.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByText() const { if (!m_textFilterSettings.text.isEmpty()) { return true; } return false; } bool ItemFilterSettings::isFilteringByTypeMime() const { if (m_mimeTypeFilter != MimeFilter::AllFiles) { return true; } return false; } bool ItemFilterSettings::isFilteringByGeolocation() const { return (m_geolocationCondition != GeolocationNoFilter); } bool ItemFilterSettings::isFilteringByRating() const { if (m_ratingFilter != 0 || m_ratingCond != GreaterEqualCondition || m_isUnratedExcluded) { return true; } return false; } bool ItemFilterSettings::isFilteringInternally() const { return (isFiltering() || !m_urlWhitelists.isEmpty() || !m_idWhitelists.isEmpty()); } bool ItemFilterSettings::isFiltering() const { return isFilteringByDay() || isFilteringByTags() || isFilteringByText() || isFilteringByRating() || isFilteringByTypeMime() || isFilteringByColorLabels() || isFilteringByPickLabels() || isFilteringByGeolocation(); } void ItemFilterSettings::setDayFilter(const QList& days) { m_dayFilter.clear(); for (QList::const_iterator it = days.constBegin(); it != days.constEnd(); ++it) { m_dayFilter.insert(*it, true); } } void ItemFilterSettings::setTagFilter(const QList& includedTags, const QList& excludedTags, MatchingCondition matchingCondition, bool showUnTagged, const QList& clTagIds, const QList& plTagIds) { m_includeTagFilter = includedTags; m_excludeTagFilter = excludedTags; m_matchingCond = matchingCondition; m_untaggedFilter = showUnTagged; m_colorLabelTagFilter = clTagIds; m_pickLabelTagFilter = plTagIds; } void ItemFilterSettings::setRatingFilter(int rating, RatingCondition ratingCondition, bool isUnratedExcluded) { m_ratingFilter = rating; m_ratingCond = ratingCondition; m_isUnratedExcluded = isUnratedExcluded; } void ItemFilterSettings::setMimeTypeFilter(int mime) { m_mimeTypeFilter = (MimeFilter::TypeMimeFilter)mime; } void ItemFilterSettings::setGeolocationFilter(const GeolocationCondition& condition) { m_geolocationCondition = condition; } void ItemFilterSettings::setTextFilter(const SearchTextFilterSettings& settings) { m_textFilterSettings = settings; } void ItemFilterSettings::setTagNames(const QHash& hash) { m_tagNameHash = hash; } void ItemFilterSettings::setAlbumNames(const QHash& hash) { m_albumNameHash = hash; } void ItemFilterSettings::setUrlWhitelist(const QList& urlList, const QString& id) { if (urlList.isEmpty()) { m_urlWhitelists.remove(id); } else { m_urlWhitelists.insert(id, urlList); } } void ItemFilterSettings::setIdWhitelist(const QList& idList, const QString& id) { if (idList.isEmpty()) { m_idWhitelists.remove(id); } else { m_idWhitelists.insert(id, idList); } } template bool containsAnyOf(const ContainerA& listA, const ContainerB& listB) { foreach (const typename ContainerA::value_type& a, listA) { if (listB.contains(a)) { return true; } } return false; } template bool containsNoneOfExcept(const ContainerA& list, const ContainerB& noneOfList, const Value& exception) { foreach (const typename ContainerB::value_type& n, noneOfList) { if (n != exception && list.contains(n)) { return false; } } return true; } bool ItemFilterSettings::matches(const ItemInfo& info, bool* const foundText) const { if (foundText) { *foundText = false; } if (!isFilteringInternally()) { return true; } bool match = false; if (!m_includeTagFilter.isEmpty() || !m_excludeTagFilter.isEmpty()) { QList tagIds = info.tagIds(); QList::const_iterator it; match = m_includeTagFilter.isEmpty(); if (m_matchingCond == OrCondition) { for (it = m_includeTagFilter.begin() ; it != m_includeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = true; break; } } match |= (m_untaggedFilter && tagIds.isEmpty()); } else // AND matching condition... { // m_untaggedFilter and non-empty tag filter, combined with AND, is logically no match if (!m_untaggedFilter) { for (it = m_includeTagFilter.begin(); it != m_includeTagFilter.end(); ++it) { if (!tagIds.contains(*it)) { break; } } if (it == m_includeTagFilter.end()) { match = true; } } } for (it = m_excludeTagFilter.begin() ; it != m_excludeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = false; break; } } } else if (m_untaggedFilter) { match = !TagsCache::instance()->containsPublicTags(info.tagIds()); } else { match = true; } //-- Filter by pick labels ------------------------------------------------ if (!m_pickLabelTagFilter.isEmpty()) { QList tagIds = info.tagIds(); bool matchPL = false; if (containsAnyOf(m_pickLabelTagFilter, tagIds)) { matchPL = true; } else { int noPickLabelTagId = TagsCache::instance()->tagForPickLabel(NoPickLabel); if (m_pickLabelTagFilter.contains(noPickLabelTagId)) { // Searching for "has no ColorLabel" requires special handling: // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag matchPL = containsNoneOfExcept(tagIds, TagsCache::instance()->pickLabelTags(), noPickLabelTagId); } } match &= matchPL; } //-- Filter by color labels ------------------------------------------------ if (!m_colorLabelTagFilter.isEmpty()) { QList tagIds = info.tagIds(); bool matchCL = false; if (containsAnyOf(m_colorLabelTagFilter, tagIds)) { matchCL = true; } else { int noColorLabelTagId = TagsCache::instance()->tagForColorLabel(NoColorLabel); if (m_colorLabelTagFilter.contains(noColorLabelTagId)) { // Searching for "has no ColorLabel" requires special handling: // Scan that the tag ids contains none of the ColorLabel tags, except maybe the NoColorLabel tag matchCL = containsNoneOfExcept(tagIds, TagsCache::instance()->colorLabelTags(), noColorLabelTagId); } } match &= matchCL; } //-- Filter by date ----------------------------------------------------------- if (!m_dayFilter.isEmpty()) { match &= m_dayFilter.contains(QDateTime(info.dateTime().date(), QTime())); } //-- Filter by rating --------------------------------------------------------- if (m_ratingFilter >= 0) { // for now we treat -1 (no rating) just like a rating of 0. int rating = info.rating(); if (rating == -1) { rating = 0; } if (m_isUnratedExcluded && rating == 0) { match = false; } else { if (m_ratingCond == GreaterEqualCondition) { // If the rating is not >=, i.e it is <, then it does not match. if (rating < m_ratingFilter) { match = false; } } else if (m_ratingCond == EqualCondition) { // If the rating is not =, i.e it is !=, then it does not match. if (rating != m_ratingFilter) { match = false; } } else { // If the rating is not <=, i.e it is >, then it does not match. if (rating > m_ratingFilter) { match = false; } } } } // -- Filter by mime type ----------------------------------------------------- switch (m_mimeTypeFilter) { // info.format is a standardized string: Only one possibility per mime type case MimeFilter::ImageFiles: { if (info.category() != DatabaseItem::Image) { match = false; } break; } case MimeFilter::JPGFiles: { if (info.format() != QLatin1String("JPG")) { match = false; } break; } case MimeFilter::PNGFiles: { if (info.format() != QLatin1String("PNG")) { match = false; } break; } case MimeFilter::HEIFFiles: { if (info.format() != QLatin1String("HEIF")) { match = false; } break; } case MimeFilter::PGFFiles: { if (info.format() != QLatin1String("PGF")) { match = false; } break; } case MimeFilter::TIFFiles: { if (info.format() != QLatin1String("TIFF")) { match = false; } break; } case MimeFilter::DNGFiles: { if (info.format() != QLatin1String("RAW-DNG")) { match = false; } break; } case MimeFilter::NoRAWFiles: { if (info.format().startsWith(QLatin1String("RAW"))) { match = false; } break; } case MimeFilter::RAWFiles: { if (!info.format().startsWith(QLatin1String("RAW"))) { match = false; } break; } case MimeFilter::MoviesFiles: { if (info.category() != DatabaseItem::Video) { match = false; } break; } case MimeFilter::AudioFiles: { if (info.category() != DatabaseItem::Audio) { match = false; } break; } case MimeFilter::RasterGraphics: { if (info.format() != QLatin1String("PSD") && // Adobe Photoshop Document info.format() != QLatin1String("PSB") && // Adobe Photoshop Big info.format() != QLatin1String("XCF") && // Gimp info.format() != QLatin1String("KRA") && // Krita info.format() != QLatin1String("ORA") // Open Raster ) { match = false; } break; } default: { // All Files: do nothing... break; } } //-- Filter by geolocation ---------------------------------------------------- if (m_geolocationCondition != GeolocationNoFilter) { if (m_geolocationCondition == GeolocationNoCoordinates) { if (info.hasCoordinates()) { match = false; } } else if (m_geolocationCondition == GeolocationHasCoordinates) { if (!info.hasCoordinates()) { match = false; } } } //-- Filter by text ----------------------------------------------------------- if (!m_textFilterSettings.text.isEmpty()) { bool textMatch = false; // Image name if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageName && info.name().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image title if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageTitle && info.title().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image comment if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageComment && info.comment().contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Tag names - foreach(int id, info.tagIds()) + foreach (int id, info.tagIds()) { if (m_textFilterSettings.textFields & SearchTextFilterSettings::TagName && m_tagNameHash.value(id).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } } // Album names if (m_textFilterSettings.textFields & SearchTextFilterSettings::AlbumName && m_albumNameHash.value(info.albumId()).contains(m_textFilterSettings.text, m_textFilterSettings.caseSensitive)) { textMatch = true; } // Image Aspect Ratio if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImageAspectRatio) { QRegExp expRatio (QLatin1String("^\\d+:\\d+$")); QRegExp expFloat (QLatin1String("^\\d+(.\\d+)?$")); if (expRatio.indexIn(m_textFilterSettings.text) > -1 && m_textFilterSettings.text.contains(QRegExp(QLatin1String(":\\d+")))) { QString trimmedTextFilterSettingsText = m_textFilterSettings.text; QStringList numberStringList = trimmedTextFilterSettingsText.split(QLatin1Char(':'), QString::SkipEmptyParts); if (numberStringList.length() == 2) { QString numString = (QString)numberStringList.at(0), denomString = (QString)numberStringList.at(1); bool canConverseNum = false; bool canConverseDenom = false; int num = numString.toInt(&canConverseNum, 10), denom = denomString.toInt(&canConverseDenom, 10); if (canConverseNum && canConverseDenom) { if (fabs(info.aspectRatio() - (double)num / denom) < 0.1) textMatch = true; } } } else if (expFloat.indexIn(m_textFilterSettings.text) > -1) { QString trimmedTextFilterSettingsText = m_textFilterSettings.text; bool canConverse = false; double ratio = trimmedTextFilterSettingsText.toDouble(&canConverse); if (canConverse) { if (fabs(info.aspectRatio() - ratio) < 0.1) textMatch = true; } } } // Image Pixel Size // See bug #341053 for details. if (m_textFilterSettings.textFields & SearchTextFilterSettings::ImagePixelSize) { QSize size = info.dimensions(); int pixelSize = size.height()*size.width(); QString text = m_textFilterSettings.text; if (text.contains(QRegExp(QLatin1String("^>\\d{1,15}$"))) && pixelSize > (text.remove(0, 1)).toInt()) { textMatch = true; } else if (text.contains(QRegExp(QLatin1String("^<\\d{1,15}$"))) && pixelSize < (text.remove(0, 1)).toInt()) { textMatch = true; } else if (text.contains(QRegExp(QLatin1String("^\\d+$"))) && pixelSize == text.toInt()) { textMatch = true; } } match &= textMatch; if (foundText) { *foundText = textMatch; } } // -- filter by URL-whitelists ------------------------------------------------ // NOTE: whitelists are always AND for now. if (match) { const QUrl url = info.fileUrl(); for (QHash>::const_iterator it = m_urlWhitelists.constBegin(); it != m_urlWhitelists.constEnd() ; ++it) { match = it->contains(url); if (!match) { break; } } } if (match) { const qlonglong id = info.id(); for (QHash >::const_iterator it = m_idWhitelists.constBegin(); it != m_idWhitelists.constEnd() ; ++it) { match = it->contains(id); if (!match) { break; } } } return match; } // ------------------------------------------------------------------------------------------------- VersionItemFilterSettings::VersionItemFilterSettings() { m_includeTagFilter = 0; m_exceptionTagFilter = 0; } VersionItemFilterSettings::VersionItemFilterSettings(const VersionManagerSettings& settings) { setVersionManagerSettings(settings); } bool VersionItemFilterSettings::operator==(const VersionItemFilterSettings& other) const { return m_excludeTagFilter == other.m_excludeTagFilter && m_exceptionLists == other.m_exceptionLists; } bool VersionItemFilterSettings::matches(const ItemInfo& info) const { if (!isFiltering()) { return true; } const qlonglong id = info.id(); for (QHash >::const_iterator it = m_exceptionLists.constBegin(); it != m_exceptionLists.constEnd() ; ++it) { if (it->contains(id)) { return true; } } bool match = true; QList tagIds = info.tagIds(); if (!tagIds.contains(m_includeTagFilter)) { for (QList::const_iterator it = m_excludeTagFilter.begin(); it != m_excludeTagFilter.end() ; ++it) { if (tagIds.contains(*it)) { match = false; break; } } } if (!match) { if (tagIds.contains(m_exceptionTagFilter)) { match = true; } } return match; } bool VersionItemFilterSettings::isHiddenBySettings(const ItemInfo& info) const { QList tagIds = info.tagIds(); - foreach(int tagId, m_excludeTagFilter) + foreach (int tagId, m_excludeTagFilter) { if (tagIds.contains(tagId)) { return true; } } return false; } bool VersionItemFilterSettings::isExemptedBySettings(const ItemInfo& info) const { return info.tagIds().contains(m_exceptionTagFilter); } void VersionItemFilterSettings::setVersionManagerSettings(const VersionManagerSettings& settings) { m_excludeTagFilter.clear(); if (!settings.enabled) { return; } if (!(settings.showInViewFlags & VersionManagerSettings::ShowOriginal)) { m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion()); } if (!(settings.showInViewFlags & VersionManagerSettings::ShowIntermediates)) { m_excludeTagFilter << TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion()); } m_includeTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion()); m_exceptionTagFilter = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::versionAlwaysVisible()); } void VersionItemFilterSettings::setExceptionList(const QList& idList, const QString& id) { if (idList.isEmpty()) { m_exceptionLists.remove(id); } else { m_exceptionLists.insert(id, idList); } } bool VersionItemFilterSettings::isFiltering() const { return !m_excludeTagFilter.isEmpty(); } bool VersionItemFilterSettings::isFilteringByTags() const { return isFiltering(); } // ------------------------------------------------------------------------------------------------- GroupItemFilterSettings::GroupItemFilterSettings() : m_allOpen(false) { } bool GroupItemFilterSettings::operator==(const GroupItemFilterSettings& other) const { return (m_allOpen == other.m_allOpen && m_openGroups == other.m_openGroups); } bool GroupItemFilterSettings::matches(const ItemInfo& info) const { if (m_allOpen) { return true; } if (info.isGrouped()) { return m_openGroups.contains(info.groupImage().id()); } return true; } void GroupItemFilterSettings::setOpen(qlonglong group, bool open) { if (open) { m_openGroups << group; } else { m_openGroups.remove(group); } } bool GroupItemFilterSettings::isOpen(qlonglong group) const { return m_openGroups.contains(group); } void GroupItemFilterSettings::setAllOpen(bool open) { m_allOpen = open; } bool GroupItemFilterSettings::isAllOpen() const { return m_allOpen; } bool GroupItemFilterSettings::isFiltering() const { return !m_allOpen; } DatabaseFields::Set GroupItemFilterSettings::watchFlags() const { return DatabaseFields::Set(DatabaseFields::ImageRelations); } } // namespace Digikam diff --git a/core/libs/database/tags/facetags.cpp b/core/libs/database/tags/facetags.cpp index 2ea1c21aac..c648b777e3 100644 --- a/core/libs/database/tags/facetags.cpp +++ b/core/libs/database/tags/facetags.cpp @@ -1,450 +1,450 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-08-08 * Description : Accessing face tags * * Copyright (C) 2010-2011 by Aditya Bhatt * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "facetags.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "coredbconstants.h" #include "tagscache.h" #include "tagregion.h" #include "tagproperties.h" namespace Digikam { // --- FaceIfacePriv ---------------------------------------------------------------------------------------- class Q_DECL_HIDDEN FaceTagsHelper { public: static QString tagPath(const QString& name, int parentId); static void makeFaceTag(int tagId, const QString& fullName); static int findFirstTagWithProperty(const QString& property, const QString& value = QString()); static int tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create); }; // --- Private methods --- int FaceTagsHelper::findFirstTagWithProperty(const QString& property, const QString& value) { QList candidates = TagsCache::instance()->tagsWithProperty(property, value); if (!candidates.isEmpty()) { return candidates.first(); } return 0; } QString FaceTagsHelper::tagPath(const QString& name, int parentId) { QString faceParentTagName = TagsCache::instance()->tagName(parentId); if ((faceParentTagName).contains(QRegExp(QLatin1String("(_Digikam_root_tag_/|/_Digikam_root_tag_|_Digikam_root_tag_)")))) { return QLatin1Char('/') + name; } else { return faceParentTagName + QLatin1Char('/') + name; } } void FaceTagsHelper::makeFaceTag(int tagId, const QString& fullName) { QString faceEngineName = fullName; /* * // find a unique FacesEngineId * for (int i=0; d->findFirstTagWithProperty(TagPropertyName::FacesEngineId(), FacesEngineId); ++i) * FacesEngineId = fullName + QString::fromUtf8(" (%1)").arg(i); */ TagProperties props(tagId); props.setProperty(TagPropertyName::person(), fullName); props.setProperty(TagPropertyName::faceEngineName(), faceEngineName); } int FaceTagsHelper::tagForName(const QString& name, int tagId, int parentId, const QString& givenFullName, bool convert, bool create) { if (name.isEmpty() && givenFullName.isEmpty() && !tagId) { return FaceTags::unknownPersonTagId(); } QString fullName = givenFullName.isNull() ? name : givenFullName; if (tagId) { if (FaceTags::isPerson(tagId)) { //qCDebug(DIGIKAM_DATABASE_LOG) << "Proposed tag is already a person"; return tagId; } else if (convert) { if (fullName.isNull()) { fullName = TagsCache::instance()->tagName(tagId); } qCDebug(DIGIKAM_DATABASE_LOG) << "Converting proposed tag to person, full name" << fullName; makeFaceTag(tagId, fullName); return tagId; } return 0; } // First attempt: Find by full name in "person" attribute QList candidates = TagsCache::instance()->tagsWithProperty(TagPropertyName::person(), fullName); - foreach(int id, candidates) + foreach (int id, candidates) { qCDebug(DIGIKAM_DATABASE_LOG) << "Candidate with set full name:" << id << fullName; if (parentId == -1) { return id; } else if (TagsCache::instance()->parentTag(id) == parentId) { return id; } } // Second attempt: Find by tag name if (parentId == -1) { candidates = TagsCache::instance()->tagsForName(name); } else { tagId = TagsCache::instance()->tagForName(name, parentId); candidates.clear(); if (tagId) { candidates << tagId; } } - foreach(int id, candidates) + foreach (int id, candidates) { // Is this tag already a person tag? if (FaceTags::isPerson(id)) { qCDebug(DIGIKAM_DATABASE_LOG) << "Found tag with name" << name << "is already a person." << id; return id; } else if (convert) { qCDebug(DIGIKAM_DATABASE_LOG) << "Converting tag with name" << name << "to a person." << id; makeFaceTag(id, fullName); return id; } } // Third: If desired, create a new tag if (create) { qCDebug(DIGIKAM_DATABASE_LOG) << "Creating new tag for name" << name << "fullName" << fullName; if (parentId == -1) { parentId = FaceTags::personParentTag(); } tagId = TagsCache::instance()->getOrCreateTag(tagPath(name, parentId)); makeFaceTag(tagId, fullName); return tagId; } return 0; } // --- public methods --- QList FaceTags::allPersonNames() { return TagsCache::instance()->tagNames(allPersonTags()); } QList FaceTags::allPersonPaths() { return TagsCache::instance()->tagPaths(allPersonTags()); } int FaceTags::tagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, false, false); } int FaceTags::getOrCreateTagForPerson(const QString& name, int parentId, const QString& fullName) { return FaceTagsHelper::tagForName(name, 0, parentId, fullName, true, true); } void FaceTags::ensureIsPerson(int tagId, const QString& fullName) { FaceTagsHelper::tagForName(QString(), tagId, 0, fullName, true, false); } bool FaceTags::isPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::person()); } bool FaceTags::isTheUnknownPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unknownPerson()); } bool FaceTags::isTheUnconfirmedPerson(int tagId) { return TagsCache::instance()->hasProperty(tagId, TagPropertyName::unconfirmedPerson()); } QList FaceTags::allPersonTags() { return TagsCache::instance()->tagsWithProperty(TagPropertyName::person()); } int FaceTags::scannedForFacesTagId() { return TagsCache::instance()->getOrCreateInternalTag(InternalTagName::scannedForFaces()); // no i18n } QMap FaceTags::identityAttributes(int tagId) { QMap attributes; QString uuid = TagsCache::instance()->propertyValue(tagId, TagPropertyName::faceEngineUuid()); if (!uuid.isEmpty()) { attributes[QLatin1String("uuid")] = uuid; } QString fullName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (!fullName.isEmpty()) { attributes[QLatin1String("fullName")] = fullName; } QString faceEngineName = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); QString tagName = TagsCache::instance()->tagName(tagId); if (tagName != faceEngineName) { attributes.insertMulti(QLatin1String("name"), faceEngineName); attributes.insertMulti(QLatin1String("name"), tagName); } else { attributes[QLatin1String("name")] = tagName; } return attributes; } void FaceTags::applyTagIdentityMapping(int tagId, const QMap& attributes) { TagProperties props(tagId); if (attributes.contains(QLatin1String("fullName"))) { props.setProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))); } // we do not change the digikam tag name at this point, but we have this extra tag property if (attributes.contains(QLatin1String("name"))) { props.setProperty(TagPropertyName::faceEngineName(), attributes.value(QLatin1String("name"))); } props.setProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))); } int FaceTags::getOrCreateTagForIdentity(const QMap& attributes) { // Attributes from FacesEngine's Identity object. // The text constants are defines in FacesEngine's API docs if (attributes.isEmpty()) { return FaceTags::unknownPersonTagId(); } int tagId; // First, look for UUID if (!attributes.value(QLatin1String("uuid")).isEmpty()) { if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineUuid(), attributes.value(QLatin1String("uuid"))))) { return tagId; } } // Second, look for full name if (!attributes.value(QLatin1String("fullName")).isEmpty()) { if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), attributes.value(QLatin1String("fullName"))))) { return tagId; } } // Third, look for either name or full name // TODO: better support for "fullName" QString name = attributes.value(QLatin1String("name")); if (name.isEmpty()) { name = attributes.value(QLatin1String("fullName")); } if (name.isEmpty()) { return FaceTags::unknownPersonTagId(); } if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::faceEngineName(), name))) { return tagId; } if ((tagId = FaceTagsHelper::findFirstTagWithProperty(TagPropertyName::person(), name))) { return tagId; } // identity is in FacesEngine's database, but not in ours, so create. tagId = FaceTagsHelper::tagForName(name, 0, -1, attributes.value(QLatin1String("fullName")), true, true); applyTagIdentityMapping(tagId, attributes); return tagId; } QString FaceTags::faceNameForTag(int tagId) { if (!TagsCache::instance()->hasTag(tagId)) { return QString(); } QString id = TagsCache::instance()->propertyValue(tagId, TagPropertyName::person()); if (id.isNull()) { id = TagsCache::instance()->tagName(tagId); } return id; } int FaceTags::personParentTag() { // check default QString i18nName = i18nc("People on your photos", "People"); int tagId = TagsCache::instance()->tagForPath(i18nName); if (tagId) { return tagId; } // employ a heuristic QList personTags = allPersonTags(); if (!personTags.isEmpty()) { // we find the most toplevel parent tag of a person tag QMultiMap tiers; - foreach(int tagId, personTags) + foreach (int tagId, personTags) { tiers.insert(TagsCache::instance()->parentTags(tagId).size(), tagId); } QList mosttoplevelTags = tiers.values(tiers.begin().key()); // as a pretty weak criterion, take the largest id which usually corresponds to the latest tag creation. std::sort(mosttoplevelTags.begin(), mosttoplevelTags.end()); return TagsCache::instance()->parentTag(mosttoplevelTags.last()); } // create default return TagsCache::instance()->getOrCreateTag(i18nName); } int FaceTags::unknownPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unknownPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of detected faces from the collections but not recognized", "Unknown"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unknownPerson(), QString()); // special property return unknownPersonTagId; } int FaceTags::unconfirmedPersonTagId() { QList ids = TagsCache::instance()->tagsWithPropertyCached(TagPropertyName::unconfirmedPerson()); if (!ids.isEmpty()) { return ids.first(); } int unknownPersonTagId = TagsCache::instance()->getOrCreateTag( FaceTagsHelper::tagPath( i18nc("The list of recognized faces from the collections but not confirmed", "Unconfirmed"), personParentTag())); TagProperties props(unknownPersonTagId); props.setProperty(TagPropertyName::person(), QString()); // no name associated props.setProperty(TagPropertyName::unconfirmedPerson(), QString()); // special property return unknownPersonTagId; } } // Namespace Digikam diff --git a/core/libs/database/tags/tagproperties.cpp b/core/libs/database/tags/tagproperties.cpp index a94a5b3894..0abeb12d52 100644 --- a/core/libs/database/tags/tagproperties.cpp +++ b/core/libs/database/tags/tagproperties.cpp @@ -1,217 +1,217 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-07-05 * Description : Access to the properties of a tag in the database * * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "tagproperties.h" // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "coredb.h" #include "coredbaccess.h" #include "tagscache.h" namespace Digikam { typedef QExplicitlySharedDataPointer TagPropertiesPrivSharedPointer; class Q_DECL_HIDDEN TagProperties::TagPropertiesPriv : public QSharedData { public: explicit TagPropertiesPriv() : tagId(-1) { } static TagPropertiesPrivSharedPointer createGuarded(int tagId); bool isNull() const; public: int tagId; QMultiMap properties; }; // ------------------------------------------------------------------------------------------------ class Q_DECL_HIDDEN TagPropertiesPrivSharedNull : public TagPropertiesPrivSharedPointer { public: TagPropertiesPrivSharedNull() : TagPropertiesPrivSharedPointer(new TagProperties::TagPropertiesPriv) { } }; Q_GLOBAL_STATIC(TagPropertiesPrivSharedNull, tagPropertiesPrivSharedNull) TagPropertiesPrivSharedPointer TagProperties::TagPropertiesPriv::createGuarded(int tagId) { if (tagId <= 0) { qCDebug(DIGIKAM_DATABASE_LOG) << "Attempt to create tag properties for tag id" << tagId; return *tagPropertiesPrivSharedNull; } return TagPropertiesPrivSharedPointer(new TagPropertiesPriv); } bool TagProperties::TagPropertiesPriv::isNull() const { return this == tagPropertiesPrivSharedNull->constData(); } // ------------------------------------------------------------------------------------------------ TagProperties::TagProperties() : d(*tagPropertiesPrivSharedNull) { } TagProperties::TagProperties(int tagId) : d(TagPropertiesPriv::createGuarded(tagId)) { if (d->isNull()) { return; } d->tagId = tagId; QList properties = CoreDbAccess().db()->getTagProperties(tagId); - foreach(const TagProperty& p, properties) + foreach (const TagProperty& p, properties) { d->properties.insert(p.property, p.value); } } TagProperties::~TagProperties() { } TagProperties::TagProperties(const TagProperties& other) { d = other.d; } TagProperties& TagProperties::operator=(const TagProperties& other) { d = other.d; return *this; } bool TagProperties::isNull() const { return d == *tagPropertiesPrivSharedNull; } TagProperties TagProperties::getOrCreate(const QString& tagPath) { int tagId = TagsCache::instance()->getOrCreateTag(tagPath); return TagProperties(tagId); } int TagProperties::tagId() const { return d->tagId; } bool TagProperties::hasProperty(const QString& key) const { return d->properties.contains(key); } bool TagProperties::hasProperty(const QString& key, const QString& value) const { return d->properties.contains(key, value); } QString TagProperties::value(const QString& key) const { return d->properties.value(key); } QStringList TagProperties::propertyKeys() const { return d->properties.keys(); } QMap TagProperties::properties() const { return d->properties; } void TagProperties::setProperty(const QString& key, const QString& value) { if (d->isNull()) { return; } if (d->properties.contains(key, value) && d->properties.count(key) == 1) { return; } // for single entries in db, this can of course be optimized using a single UPDATE WHERE removeProperties(key); d->properties.insert(key, value); CoreDbAccess().db()->addTagProperty(d->tagId, key, value); } void TagProperties::addProperty(const QString& key, const QString& value) { if (d->isNull() || d->properties.contains(key, value)) { return; } d->properties.insert(key, value); CoreDbAccess().db()->addTagProperty(d->tagId, key, value); } void TagProperties::removeProperty(const QString& key, const QString& value) { if (!d->isNull() && d->properties.contains(key, value)) { CoreDbAccess().db()->removeTagProperties(d->tagId, key, value); d->properties.remove(key, value); } } void TagProperties::removeProperties(const QString& key) { if (!d->isNull() && d->properties.contains(key)) { CoreDbAccess().db()->removeTagProperties(d->tagId, key); d->properties.remove(key); } } } // namespace Digikam diff --git a/core/libs/dialogs/deletedialog.cpp b/core/libs/dialogs/deletedialog.cpp index f799fabf87..be228b9842 100644 --- a/core/libs/dialogs/deletedialog.cpp +++ b/core/libs/dialogs/deletedialog.cpp @@ -1,679 +1,679 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-07 * Description : a dialog to delete item. * * Copyright (C) 2004 by Michael Pyne * Copyright (C) 2006 by Ian Monroe * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2006-2011 by Marcel Wiesweg * Copyright (C) 2008-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "deletedialog.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "applicationsettings.h" #include "coredburl.h" namespace Digikam { class Q_DECL_HIDDEN DeleteItem::Private { public: explicit Private() { hasThumb = false; } bool hasThumb; QUrl url; }; DeleteItem::DeleteItem(QTreeWidget* const parent, const QUrl& url) : QTreeWidgetItem(parent), d(new Private) { d->url = url; if (d->url.scheme() == QLatin1String("digikamalbums")) { if (CoreDbUrl(d->url).isAlbumUrl()) { setThumb(QIcon::fromTheme(QLatin1String("folder")).pixmap(parent->iconSize().width())); } else { setThumb(QIcon::fromTheme(QLatin1String("tag")).pixmap(parent->iconSize().width())); } } else { setThumb(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(parent->iconSize().width(), QIcon::Disabled), false); } setText(1, fileUrl()); } DeleteItem::~DeleteItem() { delete d; } bool DeleteItem::hasValidThumbnail() const { return d->hasThumb; } QUrl DeleteItem::url() const { return d->url; } QString DeleteItem::fileUrl() const { if (d->url.isLocalFile()) { return (d->url.toLocalFile()); } else if (d->url.scheme() == QLatin1String("digikamalbums")) { return (CoreDbUrl(d->url).fileUrl().toLocalFile()); } return (d->url.toDisplayString()); } void DeleteItem::setThumb(const QPixmap& pix, bool hasThumb) { int iconSize = treeWidget()->iconSize().width(); QPixmap pixmap(iconSize+2, iconSize+2); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.drawPixmap((pixmap.width()/2) - (pix.width()/2), (pixmap.height()/2) - (pix.height()/2), pix); QIcon icon = QIcon(pixmap); // We make sure the preview icon stays the same regardless of the role icon.addPixmap(pixmap, QIcon::Selected, QIcon::On); icon.addPixmap(pixmap, QIcon::Selected, QIcon::Off); icon.addPixmap(pixmap, QIcon::Active, QIcon::On); icon.addPixmap(pixmap, QIcon::Active, QIcon::Off); icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); icon.addPixmap(pixmap, QIcon::Normal, QIcon::Off); setIcon(0, icon); d->hasThumb = hasThumb; } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteItemList::Private { public: explicit Private() : iconSize(64) { thumbLoadThread = nullptr; } const int iconSize; ThumbnailLoadThread* thumbLoadThread; }; DeleteItemList::DeleteItemList(QWidget* const parent) : QTreeWidget(parent), d(new Private) { d->thumbLoadThread = ThumbnailLoadThread::defaultThread(); setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAllColumnsShowFocus(true); setIconSize(QSize(d->iconSize, d->iconSize)); setColumnCount(2); setHeaderLabels(QStringList() << i18n("Thumb") << i18n("Path")); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); header()->setSectionResizeMode(1, QHeaderView::Stretch); setToolTip(i18n("List of items that are about to be deleted.")); setWhatsThis(i18n("This is the list of items that are about to be deleted.")); connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnailLoaded(LoadingDescription,QPixmap))); } DeleteItemList::~DeleteItemList() { delete d; } void DeleteItemList::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix) { QTreeWidgetItemIterator it(this); while (*it) { DeleteItem* const item = dynamic_cast(*it); if (item && item->fileUrl() == desc.filePath) { if (!pix.isNull()) { item->setThumb(pix.scaled(d->iconSize, d->iconSize, Qt::KeepAspectRatio)); } return; } ++it; } } void DeleteItemList::drawRow(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const { DeleteItem* const item = dynamic_cast(itemFromIndex(index)); if (item && !item->hasValidThumbnail()) { d->thumbLoadThread->find(ThumbnailIdentifier(item->fileUrl())); } QTreeWidget::drawRow(p, opt, index); } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteWidget::Private { public: explicit Private() { checkBoxStack = nullptr; warningIcon = nullptr; deleteText = nullptr; numFiles = nullptr; shouldDelete = nullptr; doNotShowAgain = nullptr; fileList = nullptr; listMode = DeleteDialogMode::Files; deleteMode = DeleteDialogMode::UseTrash; } QStackedWidget* checkBoxStack; QLabel* warningIcon; QLabel* deleteText; QLabel* numFiles; QCheckBox* shouldDelete; QCheckBox* doNotShowAgain; QTreeWidget* fileList; DeleteDialogMode::ListMode listMode; DeleteDialogMode::DeleteMode deleteMode; }; DeleteWidget::DeleteWidget(QWidget* const parent) : QWidget(parent), d(new Private) { setObjectName(QLatin1String("DeleteDialogBase")); resize(540, 370); setMinimumSize(QSize(420, 320)); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->checkBoxStack = new QStackedWidget(this); QLabel* logo = new QLabel(this); logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48))); d->warningIcon = new QLabel(this); d->warningIcon->setWordWrap(false); QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); sizePolicy.setHeightForWidth(d->warningIcon->sizePolicy().hasHeightForWidth()); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); d->warningIcon->setSizePolicy(sizePolicy); d->deleteText = new QLabel(this); d->deleteText->setAlignment(Qt::AlignCenter); d->deleteText->setWordWrap(true); QHBoxLayout* hbox = new QHBoxLayout(); hbox->setSpacing(spacing); hbox->setContentsMargins(QMargins()); hbox->addWidget(logo); hbox->addWidget(d->deleteText, 10); hbox->addWidget(d->warningIcon); d->fileList = new DeleteItemList(this); d->numFiles = new QLabel(this); d->numFiles->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); d->numFiles->setWordWrap(false); d->shouldDelete = new QCheckBox(d->checkBoxStack); d->shouldDelete->setGeometry(QRect(0, 0, 542, 32)); d->shouldDelete->setToolTip(i18n("If checked, files will be permanently removed instead of being placed " "in the Trash.")); d->shouldDelete->setWhatsThis(i18n("

If this box is checked, items will be " "permanently removed instead of " "being placed in the Trash.

" "

Use this option with caution: most filesystems " "are unable to " "undelete deleted items reliably.

")); d->shouldDelete->setText(i18n("&Delete items instead of moving them to the trash")); connect(d->shouldDelete, SIGNAL(toggled(bool)), this, SLOT(slotShouldDelete(bool))); d->doNotShowAgain = new QCheckBox(d->checkBoxStack); d->doNotShowAgain->setGeometry(QRect(0, 0, 100, 30)); d->doNotShowAgain->setText(i18n("Do not &ask again")); QVBoxLayout* const vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); vbox->setSpacing(spacing); vbox->addLayout(hbox); vbox->addWidget(d->fileList, 10); vbox->addWidget(d->numFiles); vbox->addWidget(d->checkBoxStack); d->checkBoxStack->addWidget(d->shouldDelete); d->checkBoxStack->addWidget(d->doNotShowAgain); d->checkBoxStack->setCurrentWidget(d->shouldDelete); bool deleteInstead = !ApplicationSettings::instance()->getUseTrash(); slotShouldDelete(deleteInstead); d->shouldDelete->setChecked(deleteInstead); } DeleteWidget::~DeleteWidget() { delete d; } void DeleteWidget::setUrls(const QList& urls) { d->fileList->clear(); - foreach(const QUrl& url, urls) + foreach (const QUrl& url, urls) { new DeleteItem(d->fileList, url); } updateText(); } void DeleteWidget::slotShouldDelete(bool shouldDelete) { setDeleteMode(shouldDelete ? DeleteDialogMode::DeletePermanently : DeleteDialogMode::UseTrash); } void DeleteWidget::setDeleteMode(DeleteDialogMode::DeleteMode deleteMode) { d->deleteMode = deleteMode; updateText(); } void DeleteWidget::setListMode(DeleteDialogMode::ListMode listMode) { d->listMode = listMode; updateText(); } void DeleteWidget::updateText() { // set "do not ask again checkbox text if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->doNotShowAgain->setToolTip(i18n("If checked, this dialog will no longer be shown, and items will " "be directly and permanently deleted.")); d->doNotShowAgain->setWhatsThis(i18n("If this box is checked, this dialog will no longer be shown, " "and items will be directly and permanently deleted.")); } else { d->doNotShowAgain->setToolTip(i18n("If checked, this dialog will no longer be shown, and items will " "be directly moved to the Trash.")); d->doNotShowAgain->setWhatsThis(i18n("If this box is checked, this dialog will no longer be shown, " "and items will be directly moved to the Trash.")); } switch (d->listMode) { case DeleteDialogMode::Files: { // Delete files if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("These items will be permanently " "deleted from your hard disk.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("These items will be moved to Trash.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); d->numFiles->setText(i18np("1 item selected.", "%1 items selected.", d->fileList->topLevelItemCount())); } break; } case DeleteDialogMode::Albums: { // Delete albums = folders if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("These albums will be permanently " "deleted from your hard disk.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("These albums will be moved to Trash.")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); } d->numFiles->setText(i18np("1 album selected.", "%1 albums selected.", d->fileList->topLevelItemCount())); break; } case DeleteDialogMode::Subalbums: { // As above, but display additional warning if (d->deleteMode == DeleteDialogMode::DeletePermanently) { d->deleteText->setText(i18n("

These albums will be permanently " "deleted from your hard disk.

" "

Note that all subalbums " "are included in this list and will " "be deleted permanently as well.

")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("dialog-warning")).pixmap(48)); } else { d->deleteText->setText(i18n("

These albums will be moved to Trash.

" "

Note that all subalbums " "are included in this list and will " "be moved to Trash as well.

")); d->warningIcon->setPixmap(QIcon::fromTheme(QLatin1String("user-trash-full")).pixmap(48)); } d->numFiles->setText(i18np("1 album selected.", "%1 albums selected.", d->fileList->topLevelItemCount())); break; } } } //---------------------------------------------------------------------------- class Q_DECL_HIDDEN DeleteDialog::Private { public: explicit Private() { saveShouldDeleteUserPreference = true; saveDoNotShowAgainTrash = false; saveDoNotShowAgainPermanent = false; page = nullptr; buttons = nullptr; } bool saveShouldDeleteUserPreference; bool saveDoNotShowAgainTrash; bool saveDoNotShowAgainPermanent; DeleteWidget* page; QDialogButtonBox* buttons; }; DeleteDialog::DeleteDialog(QWidget* const parent) : QDialog(parent), d(new Private) { setModal(true); d->buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Cancel, this); d->buttons->button(QDialogButtonBox::Apply)->setDefault(true); d->page = new DeleteWidget(this); d->page->setMinimumSize(400, 300); QVBoxLayout* const vbx = new QVBoxLayout(this); vbx->addWidget(d->page); vbx->addWidget(d->buttons); setLayout(vbx); setMinimumSize(410, 326); adjustSize(); slotShouldDelete(shouldDelete()); connect(d->page->d->shouldDelete, SIGNAL(toggled(bool)), this, SLOT(slotShouldDelete(bool))); connect(d->buttons->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotUser1Clicked())); connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); } DeleteDialog::~DeleteDialog() { delete d; } bool DeleteDialog::confirmDeleteList(const QList& condemnedFiles, DeleteDialogMode::ListMode listMode, DeleteDialogMode::DeleteMode deleteMode) { setUrls(condemnedFiles); presetDeleteMode(deleteMode); setListMode(listMode); if (deleteMode == DeleteDialogMode::NoChoiceTrash) { if (!ApplicationSettings::instance()->getShowTrashDeleteDialog()) { return true; } } else if (deleteMode == DeleteDialogMode::NoChoiceDeletePermanently) { if (!ApplicationSettings::instance()->getShowPermanentDeleteDialog()) { return true; } } return (exec() == QDialog::Accepted); } void DeleteDialog::setUrls(const QList& urls) { d->page->setUrls(urls); } void DeleteDialog::slotUser1Clicked() { // Save user's preference ApplicationSettings* const settings = ApplicationSettings::instance(); if (d->saveShouldDeleteUserPreference) { settings->setUseTrash(!shouldDelete()); } if (d->saveDoNotShowAgainTrash) { qCDebug(DIGIKAM_GENERAL_LOG) << "setShowTrashDeleteDialog " << !d->page->d->doNotShowAgain->isChecked(); settings->setShowTrashDeleteDialog(!d->page->d->doNotShowAgain->isChecked()); } if (d->saveDoNotShowAgainPermanent) { qCDebug(DIGIKAM_GENERAL_LOG) << "setShowPermanentDeleteDialog " << !d->page->d->doNotShowAgain->isChecked(); settings->setShowPermanentDeleteDialog(!d->page->d->doNotShowAgain->isChecked()); } settings->saveSettings(); QDialog::accept(); } bool DeleteDialog::shouldDelete() const { return d->page->d->shouldDelete->isChecked(); } void DeleteDialog::slotShouldDelete(bool shouldDelete) { // This is called once from constructor, and then when the user changed the checkbox state. // In that case, save the user's preference. d->saveShouldDeleteUserPreference = true; d->buttons->button(QDialogButtonBox::Apply)->setText(shouldDelete ? i18n("&Delete") : i18n("&Move to Trash")); d->buttons->button(QDialogButtonBox::Apply)->setIcon(shouldDelete ? QIcon::fromTheme(QLatin1String("edit-delete")) : QIcon::fromTheme(QLatin1String("user-trash-full"))); } void DeleteDialog::presetDeleteMode(DeleteDialogMode::DeleteMode mode) { switch (mode) { case DeleteDialogMode::NoChoiceTrash: { // access the widget directly, signals will be fired to DeleteDialog and DeleteWidget d->page->d->shouldDelete->setChecked(false); d->page->d->checkBoxStack->setCurrentWidget(d->page->d->doNotShowAgain); d->saveDoNotShowAgainTrash = true; break; } case DeleteDialogMode::NoChoiceDeletePermanently: { d->page->d->shouldDelete->setChecked(true); d->page->d->checkBoxStack->setCurrentWidget(d->page->d->doNotShowAgain); d->saveDoNotShowAgainPermanent = true; //d->page->d->checkBoxStack->hide(); break; } case DeleteDialogMode::UserPreference: { break; } case DeleteDialogMode::UseTrash: case DeleteDialogMode::DeletePermanently: { // toggles signals which do the rest d->page->d->shouldDelete->setChecked(mode == DeleteDialogMode::DeletePermanently); // the preference set by this preset method will be ignored // for the next DeleteDialog instance and not stored as user preference. // Only if the user once changes this value, it will be taken as user preference. d->saveShouldDeleteUserPreference = false; break; } } } void DeleteDialog::setListMode(DeleteDialogMode::ListMode mode) { d->page->setListMode(mode); switch (mode) { case DeleteDialogMode::Files: setWindowTitle(i18n("About to delete selected items")); break; case DeleteDialogMode::Albums: case DeleteDialogMode::Subalbums: setWindowTitle(i18n("About to delete selected albums")); break; } } void DeleteDialog::keyPressEvent(QKeyEvent* e) { if (e->modifiers() == 0) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { if (d->buttons->button(QDialogButtonBox::Apply)->hasFocus()) { e->accept(); d->buttons->button(QDialogButtonBox::Apply)->animateClick(); return; } else if (d->buttons->button(QDialogButtonBox::Cancel)->hasFocus()) { e->accept(); d->buttons->button(QDialogButtonBox::Cancel)->animateClick(); return; } } } QDialog::keyPressEvent(e); } } // namespace Digikam diff --git a/core/libs/dimg/filters/dimgfiltermanager.cpp b/core/libs/dimg/filters/dimgfiltermanager.cpp index 6074a036d0..1b6fcc77c3 100644 --- a/core/libs/dimg/filters/dimgfiltermanager.cpp +++ b/core/libs/dimg/filters/dimgfiltermanager.cpp @@ -1,495 +1,495 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-24 * Description : manager for filters (registering, creating etc) * * Copyright (C) 2010-2011 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "dimgfiltermanager.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "dimgfiltergenerator.h" #include "dimgbuiltinfilter.h" #include "filteraction.h" #include "rawprocessingfilter.h" // Filter includes #include "antivignettingfilter.h" #include "autoexpofilter.h" #include "autolevelsfilter.h" #include "bcgfilter.h" #include "blurfilter.h" #include "blurfxfilter.h" #include "borderfilter.h" #include "bwsepiafilter.h" #include "cbfilter.h" #include "charcoalfilter.h" #include "colorfxfilter.h" #include "curvesfilter.h" #include "distortionfxfilter.h" #include "embossfilter.h" #include "equalizefilter.h" #include "filmgrainfilter.h" #include "freerotationfilter.h" #include "greycstorationfilter.h" #include "hslfilter.h" #include "icctransformfilter.h" #include "infraredfilter.h" #include "invertfilter.h" #include "lensdistortionfilter.h" #include "levelsfilter.h" #include "localcontrastfilter.h" #include "mixerfilter.h" #include "normalizefilter.h" #include "nrfilter.h" #include "oilpaintfilter.h" #include "redeyecorrectionfilter.h" #include "raindropfilter.h" #include "sharpenfilter.h" #include "shearfilter.h" #include "stretchfilter.h" #include "texturefilter.h" #include "tonalityfilter.h" #include "unsharpmaskfilter.h" #include "wbfilter.h" #include "filmfilter_p.h" #ifdef HAVE_LIBLQR_1 # include "contentawarefilter.h" #endif /* HAVE_LIBLQR_1 */ #ifdef HAVE_LENSFUN # include "lensfunfilter.h" #endif // HAVE_LENSFUN #ifdef HAVE_EIGEN3 # include "refocusfilter.h" #endif // HAVE_EIGEN3 namespace Digikam { typedef QSharedPointer ImgFilterPtr; class Q_DECL_HIDDEN DImgFilterManager::Private { public: explicit Private() : mutex(QMutex::Recursive) { } ~Private() { } void setupCoreGenerators(); void setupFilterIcons(); void setupI18nStrings(); void addGenerator(const ImgFilterPtr& generator); public: QMap filterMap; QList coreGenerators; QHash filterIcons; QHash i18nFilterNames; QMutex mutex; }; void DImgFilterManager::Private::setupCoreGenerators() { //Please keep this list sorted alphabetically coreGenerators << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) #ifdef HAVE_LIBLQR_1 << ImgFilterPtr(new BasicDImgFilterGenerator()) #endif // HAVE_LIBLQR_1 << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) #ifdef HAVE_LENSFUN << ImgFilterPtr(new BasicDImgFilterGenerator()) #endif // HAVE_LENSFUN << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) #ifdef HAVE_EIGEN3 << ImgFilterPtr(new BasicDImgFilterGenerator()) #endif // HAVE_EIGEN3 << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()) << ImgFilterPtr(new BasicDImgFilterGenerator()); } void DImgFilterManager::Private::setupFilterIcons() { //Please keep this list sorted alphabetically filterIcons.insert(QLatin1String("digikam:AntiVignettingFilter"), QLatin1String("antivignetting")); filterIcons.insert(QLatin1String("digikam:AutoExpoFilter"), QLatin1String("autocorrection")); filterIcons.insert(QLatin1String("digikam:AutoLevelsfilter"), QLatin1String("autocorrection")); filterIcons.insert(QLatin1String("digikam:BCGFilter"), QLatin1String("contrast")); filterIcons.insert(QLatin1String("digikam:BlurFilter"), QLatin1String("blurimage")); filterIcons.insert(QLatin1String("digikam:BlurFXFilter"), QLatin1String("blurfx")); filterIcons.insert(QLatin1String("digikam:BorderFilter"), QLatin1String("bordertool")); filterIcons.insert(QLatin1String("digikam:BWSepiaFilter"), QLatin1String("bwtonal")); filterIcons.insert(QLatin1String("digikam:ColorBalanceFilter"), QLatin1String("adjustrgb")); filterIcons.insert(QLatin1String("digikam:CharcoalFilter"), QLatin1String("charcoaltool")); filterIcons.insert(QLatin1String("digikam:ColorFX"), QLatin1String("colorfx")); filterIcons.insert(QLatin1String("digikam:ContentAwareFilter"), QLatin1String("transform-scale")); filterIcons.insert(QLatin1String("digikam:CurvesFilter"), QLatin1String("adjustcurves")); filterIcons.insert(QLatin1String("digikam:DistortionFXFilter"), QLatin1String("draw-spiral")); filterIcons.insert(QLatin1String("digikam:EmbossFilter"), QLatin1String("embosstool")); filterIcons.insert(QLatin1String("digikam:EqualizeFilter"), QLatin1String("autocorrection")); filterIcons.insert(QLatin1String("digikam:FilmFilter"), QLatin1String("colorneg")); filterIcons.insert(QLatin1String("digikam:FilmGrainFilter"), QLatin1String("filmgrain")); filterIcons.insert(QLatin1String("digikam:FreeRotationFilter"), QLatin1String("transform-rotate")); filterIcons.insert(QLatin1String("digikam:GreycstorationFilter"), QLatin1String("restoration")); filterIcons.insert(QLatin1String("digikam:HSLFilter"), QLatin1String("adjusthsl")); filterIcons.insert(QLatin1String("digikam:InvertFilter"), QLatin1String("edit-select-invert")); filterIcons.insert(QLatin1String("digikam:LensDistortionFilter"), QLatin1String("lensdistortion")); filterIcons.insert(QLatin1String("digikam:LensFunFilter"), QLatin1String("lensautofix")); filterIcons.insert(QLatin1String("digikam:LevelsFilter"), QLatin1String("adjustlevels")); filterIcons.insert(QLatin1String("digikam:LocalContrastFilter"), QLatin1String("contrast")); filterIcons.insert(QLatin1String("digikam:MixerFilter"), QLatin1String("channelmixer")); filterIcons.insert(QLatin1String("digikam:NoiseReductionFilter"), QLatin1String("noisereduction")); filterIcons.insert(QLatin1String("digikam:NormalizeFilter"), QLatin1String("autocorrection")); filterIcons.insert(QLatin1String("digikam:OilPaintFilter"), QLatin1String("oilpaint")); filterIcons.insert(QLatin1String("digikam:RainDropFilter"), QLatin1String("raindrop")); filterIcons.insert(QLatin1String("digikam:RatioCrop"), QLatin1String("transform-crop")); filterIcons.insert(QLatin1String("digikam:RedEyeCorrectionFilter"), QLatin1String("redeyes")); filterIcons.insert(QLatin1String("digikam:RefocusFilter"), QLatin1String("sharpenimage")); filterIcons.insert(QLatin1String("digikam:Rotate90"), QLatin1String("object-rotate-right")); filterIcons.insert(QLatin1String("digikam:Rotate270"), QLatin1String("object-rotate-left")); filterIcons.insert(QLatin1String("digikam:SharpenFilter"), QLatin1String("sharpenimage")); filterIcons.insert(QLatin1String("digikam:ShearFilter"), QLatin1String("transform-shear-left")); filterIcons.insert(QLatin1String("digikam:StretchFilter"), QLatin1String("autocorrection")); filterIcons.insert(QLatin1String("digikam:TextureFilter"), QLatin1String("texture")); filterIcons.insert(QLatin1String("digikam:TonalityFilter"), QLatin1String("contrast")); filterIcons.insert(QLatin1String("digikam:UnsharpMaskFilter"), QLatin1String("sharpenimage")); filterIcons.insert(QLatin1String("digikam:WhiteBalanceFilter"), QLatin1String("bordertool")); filterIcons.insert(QLatin1String("digikam:RawConverter"), QLatin1String("image-x-adobe-dng")); } void DImgFilterManager::Private::setupI18nStrings() { // i18nFilterNames.insert("someIdentifier", i18n("display name")); } void DImgFilterManager::Private::addGenerator(const ImgFilterPtr& generator) { QMutexLocker lock(&mutex); - foreach(const QString& id, generator->supportedFilters()) + foreach (const QString& id, generator->supportedFilters()) { if (filterMap.contains(id)) { qCDebug(DIGIKAM_DIMG_LOG) << "Attempt to register filter identifier" << id << "twice. Ignoring."; continue; } filterMap[id] = generator; } } // ----------------------------------------------------------------------------------------- class Q_DECL_HIDDEN DImgFilterManagerCreator { public: DImgFilterManager object; }; Q_GLOBAL_STATIC(DImgFilterManagerCreator, creator) DImgFilterManager* DImgFilterManager::instance() { return &creator->object; } DImgFilterManager::DImgFilterManager() : d(new Private) { QMutexLocker lock(&d->mutex); d->setupCoreGenerators(); d->setupFilterIcons(); d->setupI18nStrings(); - foreach(const ImgFilterPtr& gen, d->coreGenerators) + foreach (const ImgFilterPtr& gen, d->coreGenerators) { d->addGenerator(gen); } } DImgFilterManager::~DImgFilterManager() { delete d; } void DImgFilterManager::addGenerator(DImgFilterGenerator* const generator) { ImgFilterPtr shared(generator); d->addGenerator(shared); } void DImgFilterManager::removeGenerator(DImgFilterGenerator* const /*generator*/) { /* QMutexLocker lock(&d->mutex); QMap::iterator it; for (it = d->filterMap.begin(); it != d->filterMap.end();) { if (it.value() == generator) { it = d->filterMap.erase(it); } else { ++it; } } */ } QStringList DImgFilterManager::supportedFilters() { QMutexLocker lock(&d->mutex); return DImgBuiltinFilter::supportedFilters() + d->filterMap.keys(); } QList DImgFilterManager::supportedVersions(const QString& filterIdentifier) { if (DImgBuiltinFilter::isSupported(filterIdentifier)) { return DImgBuiltinFilter::supportedVersions(filterIdentifier); } QMutexLocker lock(&d->mutex); DImgFilterGenerator* gen = d->filterMap.value(filterIdentifier).data(); if (gen) { return gen->supportedVersions(filterIdentifier); } return QList(); } QString DImgFilterManager::displayableName(const QString& filterIdentifier) { QMutexLocker lock(&d->mutex); DImgFilterGenerator* gen = d->filterMap.value(filterIdentifier).data(); if (gen) { return gen->displayableName(filterIdentifier); } return QString(); } QString DImgFilterManager::filterIcon(const QString& filterIdentifier) { if (DImgBuiltinFilter::isSupported(filterIdentifier)) { return DImgBuiltinFilter::filterIcon(filterIdentifier); } QMutexLocker lock(&d->mutex); return d->filterIcons.value(filterIdentifier); } QString DImgFilterManager::filterIcon(const FilterAction& action) { QString iconName = filterIcon(action.identifier()); if (!iconName.isNull()) { return iconName; } return QLatin1String("document-edit"); } QString DImgFilterManager::i18nDisplayableName(const QString& filterIdentifier) { QMutexLocker lock(&d->mutex); QString name = d->i18nFilterNames.value(filterIdentifier); if (!name.isEmpty()) { return name; } if (DImgBuiltinFilter::isSupported(filterIdentifier)) { return DImgBuiltinFilter::i18nDisplayableName(filterIdentifier); } name = displayableName(filterIdentifier); if (!name.isEmpty()) { QByteArray latin1 = name.toLatin1(); QString translated = i18n(latin1.constData()); if (translated != name) { return translated; } return name; } QString digikamNamespace = QLatin1String("digikam:"); if (filterIdentifier.startsWith(digikamNamespace)) { return filterIdentifier.mid(digikamNamespace.length()); } return filterIdentifier; } QString DImgFilterManager::i18nDisplayableName(const FilterAction& action) { if (action.displayableName().isEmpty() && action.identifier().isEmpty()) { return i18n("Unknown filter"); } else { QString i18nDispName = i18nDisplayableName(action.identifier()); QString metadataDispName = action.displayableName(); if (!i18nDispName.isEmpty()) { return i18nDispName; } else if (!metadataDispName.isEmpty()) { return metadataDispName; } else { return action.identifier(); } } } bool DImgFilterManager::isSupported(const QString& filterIdentifier) { QMutexLocker lock(&d->mutex); if (DImgBuiltinFilter::isSupported(filterIdentifier)) { return true; } return d->filterMap.contains(filterIdentifier); } bool DImgFilterManager::isSupported(const QString& filterIdentifier, int version) { QMutexLocker lock(&d->mutex); if (DImgBuiltinFilter::isSupported(filterIdentifier, version)) { return true; } DImgFilterGenerator* gen = d->filterMap.value(filterIdentifier).data(); if (gen) { return gen->isSupported(filterIdentifier, version); } return false; } bool DImgFilterManager::isRawConversion(const QString& filterIdentifier) { return filterIdentifier == RawProcessingFilter::FilterIdentifier(); } DImgThreadedFilter* DImgFilterManager::createFilter(const QString& filterIdentifier, int version) { QMutexLocker lock(&d->mutex); qCDebug(DIGIKAM_DIMG_LOG) << "Creating filter " << filterIdentifier; DImgFilterGenerator* gen = d->filterMap.value(filterIdentifier).data(); if (gen) { return gen->createFilter(filterIdentifier, version); } return nullptr; } } // namespace Digikam diff --git a/core/libs/dimg/filters/filteractionfilter.cpp b/core/libs/dimg/filters/filteractionfilter.cpp index 7738623b99..80f398e621 100644 --- a/core/libs/dimg/filters/filteractionfilter.cpp +++ b/core/libs/dimg/filters/filteractionfilter.cpp @@ -1,265 +1,265 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-11-10 * Description : meta-filter to apply FilterAction * * Copyright (C) 2010-2011 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "filteractionfilter.h" // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "digikam_export.h" #include "dimgbuiltinfilter.h" #include "dimgfiltermanager.h" #include "filteraction.h" namespace Digikam { class Q_DECL_HIDDEN FilterActionFilter::Private { public: explicit Private() { continueOnError = false; } bool continueOnError; QList actions; QList appliedActions; QString errorMessage; }; FilterActionFilter::FilterActionFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } FilterActionFilter::~FilterActionFilter() { delete d; } void FilterActionFilter::setContinueOnError(bool cont) { d->continueOnError = cont; } void FilterActionFilter::setFilterActions(const QList& actions) { d->actions = actions; } void FilterActionFilter::addFilterActions(const QList& actions) { d->actions += actions; } void FilterActionFilter::setFilterAction(const FilterAction& action) { d->actions.clear(); d->actions << action; } void FilterActionFilter::addFilterAction(const FilterAction& action) { d->actions << action; } QList FilterActionFilter::filterActions() const { return d->actions; } bool FilterActionFilter::isReproducible() const { - foreach(const FilterAction& action, d->actions) + foreach (const FilterAction& action, d->actions) { if (!action.isNull() && action.category() != FilterAction::ReproducibleFilter) { return false; } } return true; } bool FilterActionFilter::isComplexAction() const { - foreach(const FilterAction& action, d->actions) + foreach (const FilterAction& action, d->actions) { if (!action.isNull() && action.category() != FilterAction::ReproducibleFilter && action.category() != FilterAction::ComplexFilter) { return false; } } return true; } bool FilterActionFilter::isSupported() const { - foreach(const FilterAction& action, d->actions) + foreach (const FilterAction& action, d->actions) { if (!action.isNull() && !DImgFilterManager::instance()->isSupported(action.identifier(), action.version())) { return false; } } return true; } bool FilterActionFilter::completelyApplied() const { return d->appliedActions.size() == d->actions.size(); } QList FilterActionFilter::appliedFilterActions() const { return d->appliedActions; } FilterAction FilterActionFilter::failedAction() const { if (d->appliedActions.size() >= d->actions.size()) { return FilterAction(); } return d->actions.at(d->appliedActions.size()); } int FilterActionFilter::failedActionIndex() const { return d->appliedActions.size(); } QString FilterActionFilter::failedActionMessage() const { return d->errorMessage; } void FilterActionFilter::filterImage() { d->appliedActions.clear(); d->errorMessage.clear(); const float progressIncrement = 1.0 / qMax(1, d->actions.size()); float progress = 0; postProgress(0); DImg img = m_orgImage; - foreach(const FilterAction& action, d->actions) + foreach (const FilterAction& action, d->actions) { qCDebug(DIGIKAM_DIMG_LOG) << "Replaying action" << action.identifier(); if (action.isNull()) { continue; } if (DImgBuiltinFilter::isSupported(action.identifier())) { DImgBuiltinFilter filter(action); if (!filter.isValid()) { d->errorMessage = i18n("Built-in transformation not supported"); if (d->continueOnError) { continue; } else { break; } } filter.apply(img); d->appliedActions << filter.filterAction(); } else { QScopedPointer filter (DImgFilterManager::instance()->createFilter(action.identifier(), action.version())); if (!filter) { d->errorMessage = i18n("Filter identifier or version is not supported"); if (d->continueOnError) { continue; } else { break; } } filter->readParameters(action); if (!filter->parametersSuccessfullyRead()) { d->errorMessage = filter->readParametersError(action); if (d->continueOnError) { continue; } else { break; } } // compute filter->setupAndStartDirectly(img, this, (int)progress, (int)(progress + progressIncrement)); img = filter->getTargetImage(); d->appliedActions << filter->filterAction(); } progress += progressIncrement; postProgress((int)progress); } m_destImage = img; } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/blurfilter.cpp b/core/libs/dimg/filters/fx/blurfilter.cpp index 4f7b89bfb9..3ce64c0890 100644 --- a/core/libs/dimg/filters/fx/blurfilter.cpp +++ b/core/libs/dimg/filters/fx/blurfilter.cpp @@ -1,288 +1,288 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-17-07 * Description : A Gaussian Blur threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009 by Andi Clemens * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "blurfilter.h" // Qt includes #include // krazy:exclude=includes #include #include // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN BlurFilter::Private { public: explicit Private() { globalProgress = 0; radius = 3; } int radius; int globalProgress; QMutex lock; }; BlurFilter::BlurFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } BlurFilter::BlurFilter(DImg* const orgImage, QObject* const parent, int radius) : DImgThreadedFilter(orgImage, parent, QLatin1String("GaussianBlur")), d(new Private) { d->radius = radius; initFilter(); } BlurFilter::BlurFilter(DImgThreadedFilter* const parentFilter, const DImg& orgImage, const DImg& destImage, int progressBegin, int progressEnd, int radius) : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd, parentFilter->filterName() + QLatin1String(": GaussianBlur")), d(new Private) { d->radius = radius; filterImage(); } BlurFilter::~BlurFilter() { cancelFilter(); delete d; } QString BlurFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Blur Filter")); } void BlurFilter::blurMultithreaded(uint start, uint stop) { bool sixteenBit = m_orgImage.sixteenBit(); int height = m_orgImage.height(); int width = m_orgImage.width(); int radius = d->radius; int oldProgress = 0; int progress = 0; uint a, r, g, b; int mx; int my; int mw; int mh; int mt; int* as = new int[width]; int* rs = new int[width]; int* gs = new int[width]; int* bs = new int[width]; for (uint y = start ; runningFlag() && (y < stop) ; ++y) { my = y - radius; mh = (radius << 1) + 1; if (my < 0) { mh += my; my = 0; } if ((my + mh) > height) { mh = height - my; } uchar* pDst8 = m_destImage.scanLine(y); unsigned short* pDst16 = reinterpret_cast(m_destImage.scanLine(y)); memset(as, 0, width * sizeof(int)); memset(rs, 0, width * sizeof(int)); memset(gs, 0, width * sizeof(int)); memset(bs, 0, width * sizeof(int)); for (int yy = 0 ; yy < mh ; ++yy) { uchar* pSrc8 = m_orgImage.scanLine(yy + my); unsigned short* pSrc16 = reinterpret_cast(m_orgImage.scanLine(yy + my)); for (int x = 0 ; x < width ; ++x) { if (sixteenBit) { bs[x] += pSrc16[0]; gs[x] += pSrc16[1]; rs[x] += pSrc16[2]; as[x] += pSrc16[3]; pSrc16 += 4; } else { bs[x] += pSrc8[0]; gs[x] += pSrc8[1]; rs[x] += pSrc8[2]; as[x] += pSrc8[3]; pSrc8 += 4; } } } if (width > ((radius << 1) + 1)) { for (int x = 0 ; x < width ; ++x) { a = 0; r = 0; g = 0; b = 0; mx = x - radius; mw = (radius << 1) + 1; if (mx < 0) { mw += mx; mx = 0; } if ((mx + mw) > width) { mw = width - mx; } mt = mw * mh; for (int xx = mx ; xx < (mw + mx) ; ++xx) { a += as[xx]; r += rs[xx]; g += gs[xx]; b += bs[xx]; } if (mt != 0) { a = a / mt; r = r / mt; g = g / mt; b = b / mt; } if (sixteenBit) { pDst16[0] = b; pDst16[1] = g; pDst16[2] = r; pDst16[3] = a; pDst16 += 4; } else { pDst8[0] = b; pDst8[1] = g; pDst8[2] = r; pDst8[3] = a; pDst8 += 4; } } } else { qCDebug(DIGIKAM_DIMG_LOG) << "Radius too small..."; } progress = (int)(((double)y * (100.0 / QThreadPool::globalInstance()->maxThreadCount())) / (stop-start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } delete [] as; delete [] rs; delete [] gs; delete [] bs; } void BlurFilter::filterImage() { if (d->radius < 1) { qCDebug(DIGIKAM_DIMG_LOG) << "Radius out of range..."; m_destImage = m_orgImage; return; } QList vals = multithreadedSteps(m_orgImage.height()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &BlurFilter::blurMultithreaded, vals[j], vals[j+1] )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } FilterAction BlurFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("radius"), d->radius); return action; } void BlurFilter::readParameters(const FilterAction& action) { d->radius = action.parameter(QLatin1String("radius")).toInt(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/blurfxfilter.cpp b/core/libs/dimg/filters/fx/blurfxfilter.cpp index 746a11df1d..471141d29e 100644 --- a/core/libs/dimg/filters/fx/blurfxfilter.cpp +++ b/core/libs/dimg/filters/fx/blurfxfilter.cpp @@ -1,1961 +1,1961 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Blur FX threaded image filter. * * Copyright 2005-2019 by Gilles Caulier * Copyright 2006-2010 by Marcel Wiesweg * * Original Blur algorithms copyrighted 2004 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #define ANGLE_RATIO 0.017453292519943295769236907685 #include "blurfxfilter.h" // C++ includes #include #include // Qt includes #include #include // krazy:exclude=includes #include // KDE includes #include // Local includes #include "dimg.h" #include "blurfilter.h" #include "randomnumbergenerator.h" namespace Digikam { class Q_DECL_HIDDEN BlurFXFilter::Private { public: explicit Private() { blurFXType = ZoomBlur; distance = 100; level = 45; randomSeed = RandomNumberGenerator::timeSeed(); } int blurFXType; int distance; int level; quint32 randomSeed; }; BlurFXFilter::BlurFXFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } BlurFXFilter::BlurFXFilter(DImg* const orgImage, QObject* const parent, int blurFXType, int distance, int level) : DImgThreadedFilter(orgImage, parent, QLatin1String("BlurFX")), d(new Private) { d->blurFXType = blurFXType; d->distance = distance; d->level = level; initFilter(); } BlurFXFilter::~BlurFXFilter() { cancelFilter(); delete d; } QString BlurFXFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Blur FX Filter")); } void BlurFXFilter::filterImage() { int w = m_orgImage.width(); int h = m_orgImage.height(); switch (d->blurFXType) { case ZoomBlur: zoomBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance); break; case RadialBlur: radialBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance); break; case FarBlur: farBlur(&m_orgImage, &m_destImage, d->distance); break; case MotionBlur: motionBlur(&m_orgImage, &m_destImage, d->distance, (double)d->level); break; case SoftenerBlur: softenerBlur(&m_orgImage, &m_destImage); break; case ShakeBlur: shakeBlur(&m_orgImage, &m_destImage, d->distance); break; case FocusBlur: focusBlur(&m_orgImage, &m_destImage, w / 2, h / 2, d->distance, d->level * 10); break; case SmartBlur: smartBlur(&m_orgImage, &m_destImage, d->distance, d->level); break; case FrostGlass: frostGlass(&m_orgImage, &m_destImage, d->distance); break; case Mosaic: mosaic(&m_orgImage, &m_destImage, d->distance, d->distance); break; } } void BlurFXFilter::zoomBlurMultithreaded(const Args& prm) { int nh, nw; int sumR, sumG, sumB, nCount=0; double lfRadius, lfNewRadius, lfAngle; DColor color; int offset; int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); double lfRadMax = qSqrt(Height * Height + Width * Width); for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { // ...we enter this loop to sum the bits // we initialize the variables sumR = sumG = sumB = nCount = 0; nw = prm.X - w; nh = prm.Y - prm.h; lfRadius = qSqrt(nw * nw + nh * nh); lfAngle = qAtan2((double)nh, (double)nw); lfNewRadius = (lfRadius * prm.Distance) / lfRadMax; for (int r = 0; runningFlag() && (r <= lfNewRadius); ++r) { // we need to calc the positions nw = (int)(prm.X - (lfRadius - r) * cos(lfAngle)); nh = (int)(prm.Y - (lfRadius - r) * sin(lfAngle)); if (IsInside(Width, Height, nw, nh)) { // read color offset = GetOffset(Width, nw, nh, bytesDepth); color.setColor(data + offset, sixteenBit); // we sum the bits sumR += color.red(); sumG += color.green(); sumB += color.blue(); ++nCount; } } if (nCount == 0) { nCount = 1; } // calculate pointer offset = GetOffset(Width, w, prm.h, bytesDepth); // read color to preserve alpha color.setColor(data + offset, sixteenBit); // now, we have to calc the arithmetic average color.setRed(sumR / nCount); color.setGreen(sumG / nCount); color.setBlue(sumB / nCount); // write color to destination color.setPixel(pResBits + offset); } } /* Function to apply the ZoomBlur effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * X, Y => Center of zoom in the image * Distance => Distance value * pArea => Preview area. * * Theory => Here we have a effect similar to RadialBlur mode Zoom from * Photoshop. The theory is very similar to RadialBlur, but has one * difference. Instead we use pixels with the same radius and * near angles, we take pixels with the same angle but near radius * This radius is always from the center to out of the image, we * calc a proportional radius from the center. */ void BlurFXFilter::zoomBlur(DImg* const orgImage, DImg* const destImage, int X, int Y, int Distance, const QRect& pArea) { if (Distance <= 1) { return; } int progress; // We working on full image. int xMin = 0; int xMax = orgImage->width(); int yMin = 0; int yMax = orgImage->height(); // If we working in preview mode, else we using the preview area. if (pArea.isValid()) { xMin = pArea.x(); xMax = pArea.x() + pArea.width(); yMin = pArea.y(); yMax = pArea.y() + pArea.height(); } QList vals = multithreadedSteps(xMax, xMin); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.X = X; prm.Y = Y; prm.Distance = Distance; // we have reached the main loop for (int h = yMin; runningFlag() && (h < yMax); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::zoomBlurMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)(h - yMin) * 100.0) / (yMax - yMin)); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::radialBlurMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int sumR, sumG, sumB, nw, nh; double Radius, Angle, AngleRad; DColor color; int offset; QScopedArrayPointer nMultArray(new double[prm.Distance * 2 + 1]); for (int i = -prm.Distance; i <= prm.Distance; ++i) { nMultArray[i + prm.Distance] = i * ANGLE_RATIO; } // number of added pixels int nCount = 0; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { // ...we enter this loop to sum the bits // we initialize the variables sumR = sumG = sumB = nCount = 0; nw = prm.X - w; nh = prm.Y - prm.h; Radius = qSqrt(nw * nw + nh * nh); AngleRad = qAtan2((double)nh, (double)nw); for (int a = -prm.Distance; runningFlag() && (a <= prm.Distance); ++a) { Angle = AngleRad + nMultArray[a + prm.Distance]; // we need to calc the positions nw = (int)(prm.X - Radius * qCos(Angle)); nh = (int)(prm.Y - Radius * qSin(Angle)); if (IsInside(Width, Height, nw, nh)) { // read color offset = GetOffset(Width, nw, nh, bytesDepth); color.setColor(data + offset, sixteenBit); // we sum the bits sumR += color.red(); sumG += color.green(); sumB += color.blue(); ++nCount; } } if (nCount == 0) { nCount = 1; } // calculate pointer offset = GetOffset(Width, w, prm.h, bytesDepth); // read color to preserve alpha color.setColor(data + offset, sixteenBit); // now, we have to calc the arithmetic average color.setRed(sumR / nCount); color.setGreen(sumG / nCount); color.setBlue(sumB / nCount); // write color to destination color.setPixel(pResBits + offset); } } /* Function to apply the radialBlur effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * X, Y => Center of radial in the image * Distance => Distance value * pArea => Preview area. * * Theory => Similar to RadialBlur from Photoshop, its an amazing effect * Very easy to understand but a little hard to implement. * We have all the image and find the center pixel. Now, we analyze * all the pixels and calc the radius from the center and find the * angle. After this, we sum this pixel with others with the same * radius, but different angles. Here I'm using degrees angles. */ void BlurFXFilter::radialBlur(DImg* const orgImage, DImg* const destImage, int X, int Y, int Distance, const QRect& pArea) { if (Distance <= 1) { return; } int progress; // We working on full image. int xMin = 0; int xMax = orgImage->width(); int yMin = 0; int yMax = orgImage->height(); // If we working in preview mode, else we using the preview area. if (pArea.isValid()) { xMin = pArea.x(); xMax = pArea.x() + pArea.width(); yMin = pArea.y(); yMax = pArea.y() + pArea.height(); } QList vals = multithreadedSteps(xMax, xMin); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.X = X; prm.Y = Y; prm.Distance = Distance; // we have reached the main loop for (int h = yMin; runningFlag() && (h < yMax); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::radialBlurMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)(h - yMin) * 100.0) / (yMax - yMin)); if (progress % 5 == 0) { postProgress(progress); } } } /* Function to apply the farBlur effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Distance => Distance value * * Theory => This is an interesting effect, the blur is applied in that * way: (the value "1" means pixel to be used in a blur calc, ok?) * e.g. With distance = 2 * |1|1|1|1|1| * |1|0|0|0|1| * |1|0|C|0|1| * |1|0|0|0|1| * |1|1|1|1|1| * We sum all the pixels with value = 1 and apply at the pixel with* * the position "C". */ void BlurFXFilter::farBlur(DImg* const orgImage, DImg* const destImage, int Distance) { if (Distance < 1) { return; } // we need to create our kernel // e.g. distance = 3, so kernel={3 1 1 2 1 1 3} QScopedArrayPointer nKern(new int[Distance * 2 + 1]); for (int i = 0; i < Distance * 2 + 1; ++i) { // the first element is 3 if (i == 0) { nKern[i] = 2; } // the center element is 2 else if (i == Distance) { nKern[i] = 3; } // the last element is 3 else if (i == Distance * 2) { nKern[i] = 3; } // all other elements will be 1 else { nKern[i] = 1; } } // now, we apply a convolution with kernel MakeConvolution(orgImage, destImage, Distance, nKern.data()); } void BlurFXFilter::motionBlurMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int nCount = prm.nCount; DColor color; int offset, sumR, sumG, sumB, nw, nh; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { // we initialize the variables sumR = sumG = sumB = 0; // ...we enter this loop to sum the bits for (int a = -prm.Distance; runningFlag() && (a <= prm.Distance); ++a) { // we need to calc the positions nw = w + prm.lpXArray[a + prm.Distance]; nh = prm.h + prm.lpYArray[a + prm.Distance]; offset = GetOffsetAdjusted(Width, Height, nw, nh, bytesDepth); color.setColor(data + offset, sixteenBit); // we sum the bits sumR += color.red(); sumG += color.green(); sumB += color.blue(); } if (nCount == 0) { nCount = 1; } // calculate pointer offset = GetOffset(Width, w, prm.h, bytesDepth); // read color to preserve alpha color.setColor(data + offset, sixteenBit); // now, we have to calc the arithmetic average color.setRed(sumR / nCount); color.setGreen(sumG / nCount); color.setBlue(sumB / nCount); // write color to destination color.setPixel(pResBits + offset); } } /* Function to apply the motionBlur effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Distance => Distance value * Angle => Angle direction (degrees) * * Theory => Similar to MotionBlur from Photoshop, the engine is very * simple to understand, we take a pixel (duh!), with the angle we * will taking near pixels. After this we blur (add and do a * division). */ void BlurFXFilter::motionBlur(DImg* const orgImage, DImg* const destImage, int Distance, double Angle) { if (Distance == 0) { return; } int progress; // we try to avoid division by 0 (zero) if (Angle == 0.0) { Angle = 360.0; } // we initialize cos and sin for a best performance double nAngX = cos((2.0 * M_PI) / (360.0 / Angle)); double nAngY = sin((2.0 * M_PI) / (360.0 / Angle)); // total of bits to be taken is given by this formula int nCount = Distance * 2 + 1; // we will alloc size and calc the possible results QScopedArrayPointer lpXArray(new int[nCount]); QScopedArrayPointer lpYArray(new int[nCount]); for (int i = 0; i < nCount; ++i) { lpXArray[i] = lround((double)(i - Distance) * nAngX); lpYArray[i] = lround((double)(i - Distance) * nAngY); } QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Distance = Distance; prm.nCount = nCount; prm.lpXArray = lpXArray.data(); prm.lpYArray = lpYArray.data(); // we have reached the main loop for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::motionBlurMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::softenerBlurMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int SomaR = 0, SomaG = 0, SomaB = 0; int Gray; DColor color, colorSoma; int offset, offsetSoma; int grayLimit = sixteenBit ? 32767 : 127; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { SomaR = SomaG = SomaB = 0; offset = GetOffset(Width, w, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); Gray = (color.red() + color.green() + color.blue()) / 3; if (Gray > grayLimit) { // 7x7 for (int a = -3; runningFlag() && (a <= 3); ++a) { for (int b = -3; runningFlag() && (b <= 3); ++b) { if ((((int)prm.h + a) < 0) || (((int)w + b) < 0)) { offsetSoma = offset; } else { offsetSoma = GetOffset(Width, (w + Lim_Max(w, b, Width)), (prm.h + Lim_Max(prm.h, a, Height)), bytesDepth); } colorSoma.setColor(data + offsetSoma, sixteenBit); SomaR += colorSoma.red(); SomaG += colorSoma.green(); SomaB += colorSoma.blue(); } } // 7*7 = 49 color.setRed(SomaR / 49); color.setGreen(SomaG / 49); color.setBlue(SomaB / 49); color.setPixel(pResBits + offset); } else { // 3x3 for (int a = -1; runningFlag() && (a <= 1); ++a) { for (int b = -1; runningFlag() && (b <= 1); ++b) { if ((((int)prm.h + a) < 0) || (((int)w + b) < 0)) { offsetSoma = offset; } else { offsetSoma = GetOffset(Width, (w + Lim_Max(w, b, Width)), (prm.h + Lim_Max(prm.h, a, Height)), bytesDepth); } colorSoma.setColor(data + offsetSoma, sixteenBit); SomaR += colorSoma.red(); SomaG += colorSoma.green(); SomaB += colorSoma.blue(); } } // 3*3 = 9 color.setRed(SomaR / 9); color.setGreen(SomaG / 9); color.setBlue(SomaB / 9); color.setPixel(pResBits + offset); } } } /* Function to apply the softenerBlur effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * * Theory => An interesting blur-like function. In dark tones we apply a * blur with 3x3 dimensions, in light tones, we apply a blur with * 5x5 dimensions. Easy, hun? */ void BlurFXFilter::softenerBlur(DImg* const orgImage, DImg* const destImage) { int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; // we have reached the main loop for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::softenerBlurMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::shakeBlurStage1Multithreaded(const Args& prm) { uint Width = prm.orgImage->width(); uint Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); DColor color; int offset, offsetLayer; int nw, nh; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { offsetLayer = GetOffset(Width, w, prm.h, bytesDepth); nh = ((prm.h + prm.Distance) >= Height) ? Height - 1 : prm.h + prm.Distance; offset = GetOffset(Width, w, nh, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(prm.layer1 + offsetLayer); nh = (((int)prm.h - prm.Distance) < 0) ? 0 : prm.h - prm.Distance; offset = GetOffset(Width, w, nh, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(prm.layer2 + offsetLayer); nw = ((w + prm.Distance) >= Width) ? Width - 1 : w + prm.Distance; offset = GetOffset(Width, nw, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(prm.layer3 + offsetLayer); nw = (((int)w - prm.Distance) < 0) ? 0 : w - prm.Distance; offset = GetOffset(Width, nw, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(prm.layer4 + offsetLayer); } } void BlurFXFilter::shakeBlurStage2Multithreaded(const Args& prm) { int Width = prm.orgImage->width(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); DColor color, color1, color2, color3, color4; int offset; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { offset = GetOffset(Width, w, prm.h, bytesDepth); // read original data to preserve alpha color.setColor(data + offset, sixteenBit); // read colors from all four layers color1.setColor(prm.layer1 + offset, sixteenBit); color2.setColor(prm.layer2 + offset, sixteenBit); color3.setColor(prm.layer3 + offset, sixteenBit); color4.setColor(prm.layer4 + offset, sixteenBit); // set color components of resulting color color.setRed((color1.red() + color2.red() + color3.red() + color4.red()) / 4); color.setGreen((color1.green() + color2.green() + color3.green() + color4.green()) / 4); color.setBlue((color1.blue() + color2.blue() + color3.blue() + color4.blue()) / 4); color.setPixel(pResBits + offset); } } /* Function to apply the shake blur effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Distance => Distance between layers (from origin) * * Theory => Similar to Fragment effect from Photoshop. We create 4 layers * each one has the same distance from the origin, but have * different positions (top, button, left and right), with these 4 * layers, we join all the pixels. */ void BlurFXFilter::shakeBlur(DImg* const orgImage, DImg* const destImage, int Distance) { int progress; int numBytes = orgImage->numBytes(); QScopedArrayPointer layer1(new uchar[numBytes]); QScopedArrayPointer layer2(new uchar[numBytes]); QScopedArrayPointer layer3(new uchar[numBytes]); QScopedArrayPointer layer4(new uchar[numBytes]); QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Distance = Distance; prm.layer1 = layer1.data(); prm.layer2 = layer2.data(); prm.layer3 = layer3.data(); prm.layer4 = layer4.data(); // we have reached the main loop for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::shakeBlurStage1Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 50.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } tasks.clear(); for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::shakeBlurStage2Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(50.0 + ((double)h * 50.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::focusBlurMultithreaded(const Args& prm) { int nBlendFactor; double lfRadius; int offset; DColor colorOrgImage, colorBlurredImage; int alpha; uchar* ptr = nullptr; // get composer for default blending DColorComposer* const composer = DColorComposer::getComposer(DColorComposer::PorterDuffNone); int Width = prm.orgImage->width(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int nw = 0; int nh = prm.Y - prm.h; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { nw = prm.X - w; lfRadius = qSqrt(nh * nh + nw * nw); if (sixteenBit) { nBlendFactor = CLAMP065535((int)(65535.0 * lfRadius / (double)prm.BlendRadius)); } else { nBlendFactor = (uchar)CLAMP0255((int)(255.0 * lfRadius / (double)prm.BlendRadius)); } // Read color values offset = GetOffset(Width, w, prm.h, bytesDepth); ptr = pResBits + offset; colorOrgImage.setColor(data + offset, sixteenBit); colorBlurredImage.setColor(ptr, sixteenBit); // Preserve alpha alpha = colorOrgImage.alpha(); // In normal mode, the image is focused in the middle // and less focused towards the border. // In inverse mode, the image is more focused towards the edge // and less focused in the middle. // This is achieved by swapping src and dest while blending. if (prm.bInversed) { // set blending alpha value as src alpha. Original value is stored above. colorOrgImage.setAlpha(nBlendFactor); // compose colors, writing to dest - colorBlurredImage composer->compose(colorBlurredImage, colorOrgImage); // restore alpha colorBlurredImage.setAlpha(alpha); // write color to destination colorBlurredImage.setPixel(ptr); } else { // set blending alpha value as src alpha. Original value is stored above. colorBlurredImage.setAlpha(nBlendFactor); // compose colors, writing to dest - colorOrgImage composer->compose(colorOrgImage, colorBlurredImage); // restore alpha colorOrgImage.setAlpha(alpha); // write color to destination colorOrgImage.setPixel(ptr); } } delete composer; } /* Function to apply the focusBlur effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * BlurRadius => Radius of blurred image. * BlendRadius => Radius of blending effect. * bInversed => If true, invert focus effect. * pArea => Preview area. * */ void BlurFXFilter::focusBlur(DImg* const orgImage, DImg* const destImage, int X, int Y, int BlurRadius, int BlendRadius, bool bInversed, const QRect& pArea) { int progress; // We working on full image. int xMin = 0; int xMax = orgImage->width(); int yMin = 0; int yMax = orgImage->height(); // If we working in preview mode, else we using the preview area. if (pArea.isValid()) { xMin = pArea.x(); xMax = pArea.x() + pArea.width(); yMin = pArea.y(); yMax = pArea.y() + pArea.height(); } if (pArea.isValid()) { //UNTESTED (unused) // We do not have access to the loop of the Gaussian blur, // so we have to cut the image that we run the effect on. int xMinBlur = xMin - BlurRadius; int xMaxBlur = xMax + BlurRadius; int yMinBlur = yMin - BlurRadius; int yMaxBlur = yMax + BlurRadius; DImg areaImage = orgImage->copy(xMinBlur, yMaxBlur, xMaxBlur - xMinBlur, yMaxBlur - yMinBlur); // cppcheck-suppress unusedScopedObject BlurFilter(this, *orgImage, *destImage, 10, 75, BlurRadius); // I am unsure about differences of 1 pixel destImage->bitBltImage(&areaImage, xMinBlur, yMinBlur); destImage->bitBltImage(orgImage, 0, 0, orgImage->width(), yMinBlur, 0, 0); destImage->bitBltImage(orgImage, 0, yMinBlur, xMinBlur, yMaxBlur - yMinBlur, 0, yMinBlur); destImage->bitBltImage(orgImage, xMaxBlur + 1, yMinBlur, orgImage->width() - xMaxBlur - 1, yMaxBlur - yMinBlur, yMaxBlur, yMinBlur); destImage->bitBltImage(orgImage, 0, yMaxBlur + 1, orgImage->width(), orgImage->height() - yMaxBlur - 1, 0, yMaxBlur); postProgress(80); } else { // copy bits for blurring memcpy(destImage->bits(), orgImage->bits(), orgImage->numBytes()); // Gaussian blur using the BlurRadius parameter. BlurFilter(this, *orgImage, *destImage, 10, 80, BlurRadius); } // Blending results. QList vals = multithreadedSteps(xMax, xMin); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.X = X; prm.Y = Y; prm.BlendRadius = BlendRadius; prm.bInversed = bInversed; // we have reached the main loop for (int h = yMin ; runningFlag() && (h < yMax) ; ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::focusBlurMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(80.0 + ((double)(h - yMin) * 20.0) / (yMax - yMin)); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::smartBlurStage1Multithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); int sumR, sumG, sumB, nCount; DColor color, radiusColor, radiusColorBlur; int offset, loopOffset; for (uint w = prm.start ; runningFlag() && (w < prm.stop) ; ++w) { // we initialize the variables sumR = sumG = sumB = nCount = 0; // read color offset = GetOffset(Width, w, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); // ...we enter this loop to sum the bits for (int a = -prm.Radius ; runningFlag() && (a <= prm.Radius) ; ++a) { // verify if is inside the rect if (IsInside(Width, Height, w + a, prm.h)) { // read color loopOffset = GetOffset(Width, w + a, prm.h, bytesDepth); radiusColor.setColor(data + loopOffset, sixteenBit); // now, we have to check if is inside the sensibility filter if (IsColorInsideTheRange(color.red(), color.green(), color.blue(), radiusColor.red(), radiusColor.green(), radiusColor.blue(), prm.StrengthRange)) { // finally we sum the bits sumR += radiusColor.red(); sumG += radiusColor.green(); sumB += radiusColor.blue(); } else { // finally we sum the bits sumR += color.red(); sumG += color.green(); sumB += color.blue(); } // increment counter ++nCount; } } if (nCount == 0) { nCount = 1; } // now, we have to calc the arithmetic average color.setRed(sumR / nCount); color.setGreen(sumG / nCount); color.setBlue(sumB / nCount); // write color to destination color.setPixel(prm.pBlur + offset); } } void BlurFXFilter::smartBlurStage2Multithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int sumR, sumG, sumB, nCount; DColor color, radiusColor, radiusColorBlur; int offset, loopOffset; for (uint h = prm.start ; runningFlag() && (h < prm.stop) ; ++h) { // we initialize the variables sumR = sumG = sumB = nCount = 0; // read color offset = GetOffset(Width, prm.w, h, bytesDepth); color.setColor(data + offset, sixteenBit); // ...we enter this loop to sum the bits for (int a = -prm.Radius ; runningFlag() && (a <= prm.Radius) ; ++a) { // verify if is inside the rect if (IsInside(Width, Height, prm.w, h + a)) { // read color loopOffset = GetOffset(Width, prm.w, h + a, bytesDepth); radiusColor.setColor(data + loopOffset, sixteenBit); // now, we have to check if is inside the sensibility filter if (IsColorInsideTheRange(color.red(), color.green(), color.blue(), radiusColor.red(), radiusColor.green(), radiusColor.blue(), prm.StrengthRange)) { radiusColorBlur.setColor(prm.pBlur + loopOffset, sixteenBit); // finally we sum the bits sumR += radiusColorBlur.red(); sumG += radiusColorBlur.green(); sumB += radiusColorBlur.blue(); } else { // finally we sum the bits sumR += color.red(); sumG += color.green(); sumB += color.blue(); } // increment counter ++nCount; } } if (nCount == 0) { nCount = 1; } // now, we have to calc the arithmetic average color.setRed(sumR / nCount); color.setGreen(sumG / nCount); color.setBlue(sumB / nCount); // write color to destination color.setPixel(pResBits + offset); } } /* Function to apply the SmartBlur effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Radius => blur matrix radius. * Strength => Color strength. * * Theory => Similar to SmartBlur from Photoshop, this function has the * same engine as Blur function, but, in a matrix with n * dimensions, we take only colors that pass by sensibility filter * The result is a clean image, not totally blurred, but a image * with correction between pixels. */ void BlurFXFilter::smartBlur(DImg* const orgImage, DImg* const destImage, int Radius, int Strength) { if (Radius <= 0) { return; } int progress; int StrengthRange = Strength; if (orgImage->sixteenBit()) { StrengthRange = (StrengthRange + 1) * 256 - 1; } QScopedArrayPointer pBlur(new uchar[orgImage->numBytes()]); // We need to copy our bits to blur bits memcpy(pBlur.data(), orgImage->bits(), orgImage->numBytes()); QList valsw = multithreadedSteps(orgImage->width()); QList valsh = multithreadedSteps(orgImage->height()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.StrengthRange = StrengthRange; prm.pBlur = pBlur.data(); prm.Radius = Radius; // we have reached the main loop for (uint h = 0 ; runningFlag() && (h < orgImage->height()) ; ++h) { for (int j = 0 ; runningFlag() && (j < valsw.count()-1) ; ++j) { prm.start = valsw[j]; prm.stop = valsw[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::smartBlurStage1Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 50.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } // we have reached the second part of main loop tasks.clear(); for (uint w = 0 ; runningFlag() && (w < orgImage->width()) ; ++w) { for (int j = 0 ; runningFlag() && (j < valsh.count()-1) ; ++j) { prm.start = valsh[j]; prm.stop = valsh[j+1]; prm.w = w; tasks.append(QtConcurrent::run(this, &BlurFXFilter::smartBlurStage2Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(50.0 + ((double)w * 50.0) / orgImage->width()); if (progress % 5 == 0) { postProgress(progress); } } } // NOTE: there is no gain to parallelize this method due to non re-entrancy of RandomColor() // (dixit RandomNumberGenerator which non re-entrant - Boost lib problem). /* Function to apply the frostGlass effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Frost => Frost value * * Theory => Similar to Diffuse effect, but the random byte is defined * in a matrix. Diffuse uses a random diagonal byte. */ void BlurFXFilter::frostGlass(DImg* const orgImage, DImg* const destImage, int Frost) { int progress; int Width = orgImage->width(); int Height = orgImage->height(); uchar* data = orgImage->bits(); bool sixteenBit = orgImage->sixteenBit(); int bytesDepth = orgImage->bytesDepth(); uchar* pResBits = destImage->bits(); Frost = (Frost < 1) ? 1 : (Frost > 10) ? 10 : Frost; int h, w; DColor color; int offset; // Randomize. RandomNumberGenerator generator; generator.seed(d->randomSeed); int range = sixteenBit ? 65535 : 255; // it is a huge optimization to allocate these here once QScopedArrayPointer IntensityCount(new uchar[range + 1]); QScopedArrayPointer AverageColorR(new uint[range + 1]); QScopedArrayPointer AverageColorG(new uint[range + 1]); QScopedArrayPointer AverageColorB(new uint[range + 1]); for (h = 0; runningFlag() && (h < Height); ++h) { for (w = 0; runningFlag() && (w < Width); ++w) { offset = GetOffset(Width, w, h, bytesDepth); // read color to preserve alpha color.setColor(data + offset, sixteenBit); // get random color from surrounding of w|h color = RandomColor(data, Width, Height, sixteenBit, bytesDepth, w, h, Frost, color.alpha(), generator, range, IntensityCount.data(), AverageColorR.data(), AverageColorG.data(), AverageColorB.data()); // write color to destination color.setPixel(pResBits + offset); } // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / Height); if (progress % 5 == 0) { postProgress(progress); } } } void BlurFXFilter::mosaicMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); DColor color; int offsetCenter, offset; for (uint w = prm.start; runningFlag() && (w < prm.stop); w += prm.SizeW) { // we have to find the center pixel for mosaic's rectangle offsetCenter = GetOffsetAdjusted(Width, Height, w + (prm.SizeW / 2), prm.h + (prm.SizeH / 2), bytesDepth); color.setColor(data + offsetCenter, sixteenBit); // now, we fill the mosaic's rectangle with the center pixel color for (uint subw = w; runningFlag() && (subw <= w + prm.SizeW); ++subw) { for (uint subh = prm.h; runningFlag() && (subh <= prm.h + prm.SizeH); ++subh) { // if is inside... if (IsInside(Width, Height, subw, subh)) { // set color offset = GetOffset(Width, subw, subh, bytesDepth); color.setPixel(pResBits + offset); } } } } } /* Function to apply the mosaic effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Size => Size of mosaic. * * Theory => Ok, you can find some mosaic effects on PSC, but this one * has a great feature, if you see a mosaic in other code you will * see that the corner pixel doesn't change. The explanation is * simple, the color of the mosaic is the same as the first pixel * get. Here, the color of the mosaic is the same as the mosaic * center pixel. * Now the function scan the rows from the top (like photoshop). */ void BlurFXFilter::mosaic(DImg* const orgImage, DImg* const destImage, int SizeW, int SizeH) { int progress; // we need to check for valid values if (SizeW < 1) { SizeW = 1; } if (SizeH < 1) { SizeH = 1; } if ((SizeW == 1) && (SizeH == 1)) { return; } QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.SizeW = SizeW; prm.SizeH = SizeH; // this loop will never look for transparent colors for (uint h = 0; runningFlag() && (h < orgImage->height()); h += SizeH) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::mosaicMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } /* Function to get a color in a matrix with a determined size * * Bits => Bits array * Width => Image width * Height => Image height * X => Position horizontal * Y => Position vertical * Radius => The radius of the matrix to be created * * Theory => This function takes from a distinct matrix a random color */ DColor BlurFXFilter::RandomColor(uchar* const Bits, int Width, int Height, bool sixteenBit, int bytesDepth, int X, int Y, int Radius, int alpha, RandomNumberGenerator& generator, int range, uchar* const IntensityCount, uint* const AverageColorR, uint* const AverageColorG, uint* const AverageColorB) { DColor color; int offset; int w, h, counter = 0; int I; // For 16 bit we have a problem here because this takes 255 times longer, // and the algorithm is really slow for 16 bit, but I think this cannot be avoided. memset(IntensityCount, 0, (range + 1) * sizeof(uchar)); memset(AverageColorR, 0, (range + 1) * sizeof(uint)); memset(AverageColorG, 0, (range + 1) * sizeof(uint)); memset(AverageColorB, 0, (range + 1) * sizeof(uint)); for (w = X - Radius; runningFlag() && (w <= X + Radius); ++w) { for (h = Y - Radius; runningFlag() && (h <= Y + Radius); ++h) { if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height)) { offset = GetOffset(Width, w, h, bytesDepth); color.setColor(Bits + offset, sixteenBit); I = GetIntensity(color.red(), color.green(), color.blue()); IntensityCount[I]++; ++counter; if (IntensityCount[I] == 1) { AverageColorR[I] = color.red(); AverageColorG[I] = color.green(); AverageColorB[I] = color.blue(); } else { AverageColorR[I] += color.red(); AverageColorG[I] += color.green(); AverageColorB[I] += color.blue(); } } } } // check for runningFlag here before entering the do loop (will crash with SIGFPE otherwise) if (!runningFlag()) { return DColor(0, 0, 0, 0, sixteenBit); } int RandNumber, count, Index, ErrorCount = 0; int J; do { RandNumber = generator.number(0, counter); count = 0; Index = 0; do { count += IntensityCount[Index]; ++Index; } while (runningFlag() && (count < RandNumber)); J = Index - 1; ++ErrorCount; } while (runningFlag() && (IntensityCount[J] == 0) && (ErrorCount <= counter)); if (!runningFlag()) { return DColor(0, 0, 0, 0, sixteenBit); } color.setSixteenBit(sixteenBit); color.setAlpha(alpha); int clampMax = sixteenBit ? 655535 : 255; if (ErrorCount >= counter) { if (counter == 0) { counter = 1; } color.setRed(CLAMP((int)(AverageColorR[J] / counter), 0, clampMax)); color.setGreen(CLAMP((int)(AverageColorG[J] / counter), 0, clampMax)); color.setBlue(CLAMP((int)(AverageColorB[J] / counter), 0, clampMax)); } else { if (IntensityCount[J] == 0) { IntensityCount[J] = 1; } color.setRed(CLAMP((int)(AverageColorR[J] / IntensityCount[J]), 0, clampMax)); color.setGreen(CLAMP((int)(AverageColorG[J] / IntensityCount[J]), 0, clampMax)); color.setBlue(CLAMP((int)(AverageColorB[J] / IntensityCount[J]), 0, clampMax)); } return color; } void BlurFXFilter::MakeConvolutionStage1Multithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); int n; int nSumR, nSumG, nSumB, nCount; DColor color; int offset; for (uint w = prm.start; runningFlag() && (w < prm.stop); ++w) { // initialize the variables nSumR = nSumG = nSumB = nCount = 0; // first of all, we need to blur the horizontal lines for (n = -prm.Radius; runningFlag() && (n <= prm.Radius); ++n) { // if is inside... if (IsInside(Width, Height, w + n, prm.h)) { // read color from orgImage offset = GetOffset(Width, w + n, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); // finally, we sum the pixels using a method similar to assigntables nSumR += prm.arrMult[n + prm.Radius][color.red()]; nSumG += prm.arrMult[n + prm.Radius][color.green()]; nSumB += prm.arrMult[n + prm.Radius][color.blue()]; // we need to add the kernel value to the counter nCount += prm.Kernel[n + prm.Radius]; } } if (nCount == 0) { nCount = 1; } // calculate pointer offset = GetOffset(Width, w, prm.h, bytesDepth); // read color from orgImage to preserve alpha color.setColor(data + offset, sixteenBit); // now, we have to calc the arithmetic average if (sixteenBit) { color.setRed(CLAMP065535(nSumR / nCount)); color.setGreen(CLAMP065535(nSumG / nCount)); color.setBlue(CLAMP065535(nSumB / nCount)); } else { color.setRed((uchar)CLAMP0255(nSumR / nCount)); color.setGreen((uchar)CLAMP0255(nSumG / nCount)); color.setBlue((uchar)CLAMP0255(nSumB / nCount)); } // write color to blur bits color.setPixel(prm.pBlur + offset); } } void BlurFXFilter::MakeConvolutionStage2Multithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pOutBits = prm.destImage->bits(); int n; int nSumR, nSumG, nSumB, nCount; DColor color; int offset; for (uint h = prm.start; runningFlag() && (h < prm.stop); ++h) { // initialize the variables nSumR = nSumG = nSumB = nCount = 0; // first of all, we need to blur the vertical lines for (n = -prm.Radius; runningFlag() && (n <= prm.Radius); ++n) { // if is inside... if (IsInside(Width, Height, prm.w, h + n)) { // read color from blur bits offset = GetOffset(Width, prm.w, h + n, bytesDepth); color.setColor(prm.pBlur + offset, sixteenBit); // finally, we sum the pixels using a method similar to assigntables nSumR += prm.arrMult[n + prm.Radius][color.red()]; nSumG += prm.arrMult[n + prm.Radius][color.green()]; nSumB += prm.arrMult[n + prm.Radius][color.blue()]; // we need to add the kernel value to the counter nCount += prm.Kernel[n + prm.Radius]; } } if (nCount == 0) { nCount = 1; } // calculate pointer offset = GetOffset(Width, prm.w, h, bytesDepth); // read color from orgImage to preserve alpha color.setColor(data + offset, sixteenBit); // now, we have to calc the arithmetic average if (sixteenBit) { color.setRed(CLAMP065535(nSumR / nCount)); color.setGreen(CLAMP065535(nSumG / nCount)); color.setBlue(CLAMP065535(nSumB / nCount)); } else { color.setRed((uchar)CLAMP0255(nSumR / nCount)); color.setGreen((uchar)CLAMP0255(nSumG / nCount)); color.setBlue((uchar)CLAMP0255(nSumB / nCount)); } // write color to destination color.setPixel(pOutBits + offset); } } /* Function to simple convolve a unique pixel with a determined radius * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Radius => kernel radius, e.g. rad=1, so array will be 3X3 * Kernel => kernel array to apply. * * Theory => I've worked hard here, but I think this is a very smart * way to convolve an array, its very hard to explain how I reach * this, but the trick here its to store the sum used by the * previous pixel, so we sum with the other pixels that wasn't get */ void BlurFXFilter::MakeConvolution(DImg* const orgImage, DImg* const destImage, int Radius, int Kernel[]) { if (Radius <= 0) { return; } int progress; int nKernelWidth = Radius * 2 + 1; int range = orgImage->sixteenBit() ? 65536 : 256; QScopedArrayPointer pBlur(new uchar[orgImage->numBytes()]); // We need to copy our bits to blur bits memcpy(pBlur.data(), orgImage->bits(), orgImage->numBytes()); // We need to alloc a 2d array to help us to store the values int** const arrMult = Alloc2DArray(nKernelWidth, range); for (int i = 0; i < nKernelWidth; ++i) { for (int j = 0; j < range; ++j) { arrMult[i][j] = j * Kernel[i]; } } QList valsw = multithreadedSteps(orgImage->width()); QList valsh = multithreadedSteps(orgImage->height()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Radius = Radius; prm.Kernel = Kernel; prm.arrMult = arrMult; prm.pBlur = pBlur.data(); // Now, we enter in the first loop for (uint h = 0; runningFlag() && (h < orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < valsw.count()-1) ; ++j) { prm.start = valsw[j]; prm.stop = valsw[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &BlurFXFilter::MakeConvolutionStage1Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 50.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } // We enter in the second main loop tasks.clear(); for (uint w = 0; runningFlag() && (w < orgImage->width()); ++w) { for (int j = 0 ; runningFlag() && (j < valsh.count()-1) ; ++j) { prm.start = valsh[j]; prm.stop = valsh[j+1]; prm.w = w; tasks.append(QtConcurrent::run(this, &BlurFXFilter::MakeConvolutionStage2Multithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(50.0 + ((double)w * 50.0) / orgImage->width()); if (progress % 5 == 0) { postProgress(progress); } } // now, we must free memory Free2DArray(arrMult, nKernelWidth); } FilterAction BlurFXFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("type"), d->blurFXType); action.addParameter(QLatin1String("distance"), d->distance); action.addParameter(QLatin1String("level"), d->level); if (d->blurFXType == FrostGlass) { action.addParameter(QLatin1String("randomSeed"), d->randomSeed); } return action; } void BlurFXFilter::readParameters(const FilterAction& action) { d->blurFXType = action.parameter(QLatin1String("type")).toInt(); d->distance = action.parameter(QLatin1String("distance")).toInt(); d->level = action.parameter(QLatin1String("level")).toInt(); if (d->blurFXType == FrostGlass) { d->randomSeed = action.parameter(QLatin1String("randomSeed")).toUInt(); } } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/distortionfxfilter.cpp b/core/libs/dimg/filters/fx/distortionfxfilter.cpp index ab8c3658d4..c01613a071 100644 --- a/core/libs/dimg/filters/fx/distortionfxfilter.cpp +++ b/core/libs/dimg/filters/fx/distortionfxfilter.cpp @@ -1,1288 +1,1288 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-07-18 * Description : Distortion FX threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * Original Distortion algorithms copyrighted 2004-2005 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #define ANGLE_RATIO 0.017453292519943295769236907685 #include "distortionfxfilter.h" // C++ includes #include // Qt includes #include #include #include #include // krazy:exclude=includes #include // KDE includes #include // Local includes #include "dimg.h" #include "dpixelsaliasfilter.h" #include "randomnumbergenerator.h" namespace Digikam { class Q_DECL_HIDDEN DistortionFXFilter::Private { public: explicit Private() { antiAlias = true; level = 0; iteration = 0; effectType = 0; randomSeed = 0; globalProgress = 0; } bool antiAlias; int level; int iteration; int effectType; quint32 randomSeed; RandomNumberGenerator generator; int globalProgress; QMutex lock; QMutex lock2; // RandomNumberGenerator is not re-entrant (dixit Boost lib) }; DistortionFXFilter::DistortionFXFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } DistortionFXFilter::DistortionFXFilter(DImg* const orgImage, QObject* const parent, int effectType, int level, int iteration, bool antialiaqSing) : DImgThreadedFilter(orgImage, parent, QLatin1String("DistortionFX")), d(new Private) { d->effectType = effectType; d->level = level; d->iteration = iteration; d->antiAlias = antialiaqSing; d->randomSeed = RandomNumberGenerator::timeSeed(); initFilter(); } DistortionFXFilter::~DistortionFXFilter() { cancelFilter(); delete d; } QString DistortionFXFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Distortion Effect")); } void DistortionFXFilter::filterImage() { int w = m_orgImage.width(); int h = m_orgImage.height(); int l = d->level; int f = d->iteration; switch (d->effectType) { case FishEye: fisheye(&m_orgImage, &m_destImage, (double)(l / 5.0), d->antiAlias); break; case Twirl: twirl(&m_orgImage, &m_destImage, l, d->antiAlias); break; case CilindricalHor: cilindrical(&m_orgImage, &m_destImage, (double)l, true, false, d->antiAlias); break; case CilindricalVert: cilindrical(&m_orgImage, &m_destImage, (double)l, false, true, d->antiAlias); break; case CilindricalHV: cilindrical(&m_orgImage, &m_destImage, (double)l, true, true, d->antiAlias); break; case Caricature: fisheye(&m_orgImage, &m_destImage, (double)(-l / 5.0), d->antiAlias); break; case MultipleCorners: multipleCorners(&m_orgImage, &m_destImage, l, d->antiAlias); break; case WavesHorizontal: waves(&m_orgImage, &m_destImage, l, f, true, true); break; case WavesVertical: waves(&m_orgImage, &m_destImage, l, f, true, false); break; case BlockWaves1: blockWaves(&m_orgImage, &m_destImage, l, f, false); break; case BlockWaves2: blockWaves(&m_orgImage, &m_destImage, l, f, true); break; case CircularWaves1: circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 0.0, false, d->antiAlias); break; case CircularWaves2: circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 25.0, true, d->antiAlias); break; case PolarCoordinates: polarCoordinates(&m_orgImage, &m_destImage, true, d->antiAlias); break; case UnpolarCoordinates: polarCoordinates(&m_orgImage, &m_destImage, false, d->antiAlias); break; case Tile: tile(&m_orgImage, &m_destImage, 210 - f, 210 - f, l); break; } } void DistortionFXFilter::fisheyeMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); double nh, nw, tw; DColor color; int offset; int nHalfW = Width / 2; int nHalfH = Height / 2; double lfXScale = 1.0; double lfYScale = 1.0; double lfCoeffStep = prm.Coeff / 1000.0; double lfRadius, lfAngle; if (Width > Height) { lfYScale = (double)Width / (double)Height; } else if (Height > Width) { lfXScale = (double)Height / (double)Width; } double lfRadMax = (double)qMax(Height, Width) / 2.0; double lfCoeff = lfRadMax / qLn(qFabs(lfCoeffStep) * lfRadMax + 1.0); double th = lfYScale * (double)(prm.h - nHalfH); for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { tw = lfXScale * (double)(w - nHalfW); // we find the distance from the center lfRadius = qSqrt(th * th + tw * tw); if (lfRadius < lfRadMax) { lfAngle = qAtan2(th, tw); if (prm.Coeff > 0.0) { lfRadius = (qExp(lfRadius / lfCoeff) - 1.0) / lfCoeffStep; } else { lfRadius = lfCoeff * qLn(1.0 + (-1.0 * lfCoeffStep) * lfRadius); } nw = (double)nHalfW + (lfRadius / lfXScale) * qCos(lfAngle); nh = (double)nHalfH + (lfRadius / lfYScale) * qSin(lfAngle); setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } else { // copy pixel offset = getOffset(Width, w, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(pResBits + offset); } } } /* Function to apply the fisheye effect backported from ImageProcesqSing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Coeff => Distortion effect coeff. Positive value render 'Fish Eyes' effect, * and negative values render 'Caricature' effect. * Antialias => Smart blurring result. * * Theory => This is a great effect if you take employee photos * Its pure trigonometry. I think if you study hard the code you * understand very well. */ void DistortionFXFilter::fisheye(DImg* orgImage, DImg* destImage, double Coeff, bool AntiAlias) { if (Coeff == 0.0) { return; } int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Coeff = Coeff; prm.AntiAlias = AntiAlias; // main loop for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::fisheyeMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)(h) * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::twirlMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); DColor color; int offset; int nHalfW = Width / 2; int nHalfH = Height / 2; double lfXScale = 1.0; double lfYScale = 1.0; double lfAngle, lfNewAngle, lfAngleSum, lfCurrentRadius; double tw, nh, nw; if (Width > Height) { lfYScale = (double)Width / (double)Height; } else if (Height > Width) { lfXScale = (double)Height / (double)Width; } // the angle step is dist divided by 10000 double lfAngleStep = prm.dist / 10000.0; // now, we get the minimum radius double lfRadMax = (double)qMax(Width, Height) / 2.0; double th = lfYScale * (double)(prm.h - nHalfH); for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { tw = lfXScale * (double)(w - nHalfW); // now, we get the distance lfCurrentRadius = qSqrt(th * th + tw * tw); // if distance is less than maximum radius... if (lfCurrentRadius < lfRadMax) { // we find the angle from the center lfAngle = qAtan2(th, tw); // we get the accumulated angle lfAngleSum = lfAngleStep * (-1.0 * (lfCurrentRadius - lfRadMax)); // ok, we sum angle with accumulated to find a new angle lfNewAngle = lfAngle + lfAngleSum; // now we find the exact position's x and y nw = (double)nHalfW + qCos(lfNewAngle) * (lfCurrentRadius / lfXScale); nh = (double)nHalfH + qSin(lfNewAngle) * (lfCurrentRadius / lfYScale); setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } else { // copy pixel offset = getOffset(Width, w, prm.h, bytesDepth); color.setColor(data + offset, sixteenBit); color.setPixel(pResBits + offset); } } } /* Function to apply the twirl effect backported from ImageProcesqSing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * dist => Distance value. * Antialias => Smart blurring result. * * Theory => Take spiral studies, you will understand better, I'm studying * hard on this effect, because it is not too fast. */ void DistortionFXFilter::twirl(DImg* orgImage, DImg* destImage, int dist, bool AntiAlias) { // if dist value is zero, we do nothing if (dist == 0) { return; } int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.dist = dist; prm.AntiAlias = AntiAlias; // main loop for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::twirlMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::cilindricalMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); double nh, nw; int nHalfW = Width / 2; int nHalfH = Height / 2; double lfCoeffX = 1.0; double lfCoeffY = 1.0; double lfCoeffStep = prm.Coeff / 1000.0; if (prm.Horizontal) { lfCoeffX = (double)nHalfW / qLn(qFabs(lfCoeffStep) * nHalfW + 1.0); } if (prm.Vertical) { lfCoeffY = (double)nHalfH / qLn(qFabs(lfCoeffStep) * nHalfH + 1.0); } for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { // we find the distance from the center nh = qFabs((double)(prm.h - nHalfH)); nw = qFabs((double)(w - nHalfW)); if (prm.Horizontal) { if (prm.Coeff > 0.0) { nw = (qExp(nw / lfCoeffX) - 1.0) / lfCoeffStep; } else { nw = lfCoeffX * qLn(1.0 + (-1.0 * lfCoeffStep) * nw); } } if (prm.Vertical) { if (prm.Coeff > 0.0) { nh = (qExp(nh / lfCoeffY) - 1.0) / lfCoeffStep; } else { nh = lfCoeffY * qLn(1.0 + (-1.0 * lfCoeffStep) * nh); } } nw = (double)nHalfW + ((w >= nHalfW) ? nw : -nw); nh = (double)nHalfH + ((prm.h >= nHalfH) ? nh : -nh); setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } } /* Function to apply the Cylindrical effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Coeff => Cylindrical value. * Horizontal => Apply horizontally. * Vertical => Apply vertically. * Antialias => Smart blurring result. * * Theory => This is a great effect, similar to Spherize (Photoshop). * If you understand FishEye, you will understand Cylindrical * FishEye apply a logarithm function using a sphere radius, * Spherize use the same function but in a rectangular * environment. */ void DistortionFXFilter::cilindrical(DImg* orgImage, DImg* destImage, double Coeff, bool Horizontal, bool Vertical, bool AntiAlias) { if ((Coeff == 0.0) || (!(Horizontal || Vertical))) { return; } int progress; // initial copy memcpy(destImage->bits(), orgImage->bits(), orgImage->numBytes()); QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Coeff = Coeff; prm.Horizontal = Horizontal; prm.Vertical = Vertical; prm.AntiAlias = AntiAlias; // main loop for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::cilindricalMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::multipleCornersMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); double nh, nw; int nHalfW = Width / 2; int nHalfH = Height / 2; double lfRadMax = qSqrt(Height * Height + Width * Width) / 2.0; double lfAngle, lfNewRadius, lfCurrentRadius; for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { // we find the distance from the center nh = nHalfH - prm.h; nw = nHalfW - w; // now, we get the distance lfCurrentRadius = qSqrt(nh * nh + nw * nw); // we find the angle from the center lfAngle = qAtan2(nh, nw) * (double)prm.Factor; // ok, we sum angle with accumulated to find a new angle lfNewRadius = lfCurrentRadius * lfCurrentRadius / lfRadMax; // now we find the exact position's x and y nw = (double)nHalfW - (qCos(lfAngle) * lfNewRadius); nh = (double)nHalfH - (qSin(lfAngle) * lfNewRadius); setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } } /* Function to apply the Multiple Corners effect backported from ImageProcessing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Factor => nb corners. * Antialias => Smart blurring result. * * Theory => This is an amazing function, you've never seen this before. * I was testing some trigonometric functions, and I saw that if * I multiply the angle by 2, the result is an image like this * If we multiply by 3, we can create the SixCorners effect. */ void DistortionFXFilter::multipleCorners(DImg* orgImage, DImg* destImage, int Factor, bool AntiAlias) { if (Factor == 0) { return; } int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Factor = Factor; prm.AntiAlias = AntiAlias; // main loop for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::multipleCornersMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::wavesHorizontalMultithreaded(const Args& prm) { int oldProgress=0, progress=0, tx; for (int h = prm.start; runningFlag() && (h < prm.stop); ++h) { tx = lround(prm.Amplitude * qSin((prm.Frequency * 2) * h * (M_PI / 180))); prm.destImage->bitBltImage(prm.orgImage, 0, h, prm.orgImage->width(), 1, tx, h); if (prm.FillSides) { prm.destImage->bitBltImage(prm.orgImage, prm.orgImage->width() - tx, h, tx, 1, 0, h); prm.destImage->bitBltImage(prm.orgImage, 0, h, prm.orgImage->width() - (prm.orgImage->width() - 2 * prm.Amplitude + tx), 1, prm.orgImage->width() + tx, h); } // Update the progress bar in dialog. progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } } void DistortionFXFilter::wavesVerticalMultithreaded(const Args& prm) { int oldProgress=0, progress=0, ty; for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { ty = lround(prm.Amplitude * qSin((prm.Frequency * 2) * w * (M_PI / 180))); prm.destImage->bitBltImage(prm.orgImage, w, 0, 1, prm.orgImage->height(), w, ty); if (prm.FillSides) { prm.destImage->bitBltImage(prm.orgImage, w, prm.orgImage->height() - ty, 1, ty, w, 0); prm.destImage->bitBltImage(prm.orgImage, w, 0, 1, prm.orgImage->height() - (prm.orgImage->height() - 2 * prm.Amplitude + ty), w, prm.orgImage->height() + ty); } // Update the progress bar in dialog. progress = (int)( ( (double)w * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } } /* Function to apply the waves effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Amplitude => Sinusoidal maximum height. * Frequency => Frequency value. * FillSides => Like a boolean variable. * Direction => Vertical or horizontal flag. * * Theory => This is an amazing effect, very funny, and very simple to * understand. You just need understand how qSin and qCos works. */ void DistortionFXFilter::waves(DImg* orgImage, DImg* destImage, int Amplitude, int Frequency, bool FillSides, bool Direction) { if (Amplitude < 0) { Amplitude = 0; } if (Frequency < 0) { Frequency = 0; } Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Amplitude = Amplitude; prm.Frequency = Frequency; prm.FillSides = FillSides; if (Direction) // Horizontal { QList vals = multithreadedSteps(orgImage->height()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::wavesHorizontalMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } else { QList vals = multithreadedSteps(orgImage->width()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::wavesVerticalMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } } void DistortionFXFilter::blockWavesMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int nw, nh; DColor color; int offset, offsetOther; int nHalfW = Width / 2; int nHalfH = Height / 2; for (int h = prm.start; runningFlag() && (h < prm.stop); ++h) { nw = nHalfW - prm.w; nh = nHalfH - h; if (prm.Mode) { nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * nw * (M_PI / 180))); nh = (int)(h + prm.Amplitude * qCos(prm.Frequency * nh * (M_PI / 180))); } else { nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * prm.w * (M_PI / 180))); nh = (int)(h + prm.Amplitude * qCos(prm.Frequency * h * (M_PI / 180))); } offset = getOffset(Width, prm.w, h, bytesDepth); offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth); // read color color.setColor(data + offsetOther, sixteenBit); // write color to destination color.setPixel(pResBits + offset); } } /* Function to apply the block waves effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Amplitude => Sinusoidal maximum height * Frequency => Frequency value * Mode => The mode to be applied. * * Theory => This is an amazing effect, very funny when amplitude and * frequency are small values. */ void DistortionFXFilter::blockWaves(DImg* orgImage, DImg* destImage, int Amplitude, int Frequency, bool Mode) { if (Amplitude < 0) { Amplitude = 0; } if (Frequency < 0) { Frequency = 0; } int progress; QList vals = multithreadedSteps(orgImage->height()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Mode = Mode; prm.Frequency = Frequency; prm.Amplitude = Amplitude; for (int w = 0; runningFlag() && (w < (int)orgImage->width()); ++w) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.w = w; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::blockWavesMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)w * 100.0) / orgImage->width()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::circularWavesMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); double nh, nw; double lfRadius, lfRadMax; double lfNewAmp = prm.Amplitude; double lfFreqAngle = prm.Frequency * ANGLE_RATIO; double phase = prm.Phase * ANGLE_RATIO; lfRadMax = qSqrt(Height * Height + Width * Width); for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { nw = prm.X - w; nh = prm.Y - prm.h; lfRadius = qSqrt(nw * nw + nh * nh); if (prm.WavesType) { lfNewAmp = prm.Amplitude * lfRadius / lfRadMax; } nw = (double)w + lfNewAmp * qSin(lfFreqAngle * lfRadius + phase); nh = (double)prm.h + lfNewAmp * qCos(lfFreqAngle * lfRadius + phase); setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } } /* Function to apply the circular waves effect backported from ImageProcesqSing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * X, Y => Position of circle center on the image. * Amplitude => Sinusoidal maximum height * Frequency => Frequency value. * Phase => Phase value. * WavesType => If true the amplitude is proportional to radius. * Antialias => Smart blurring result. * * Theory => Similar to Waves effect, but here I apply a sinusoidal function * with the angle point. */ void DistortionFXFilter::circularWaves(DImg* orgImage, DImg* destImage, int X, int Y, double Amplitude, double Frequency, double Phase, bool WavesType, bool AntiAlias) { if (Amplitude < 0.0) { Amplitude = 0.0; } if (Frequency < 0.0) { Frequency = 0.0; } int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Phase = Phase; prm.Frequency = Frequency; prm.Amplitude = Amplitude; prm.WavesType = WavesType; prm.X = X; prm.Y = Y; prm.AntiAlias = AntiAlias; for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::circularWavesMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::polarCoordinatesMultithreaded(const Args& prm) { int Width = prm.orgImage->width(); int Height = prm.orgImage->height(); uchar* data = prm.orgImage->bits(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* pResBits = prm.destImage->bits(); int nHalfW = Width / 2; int nHalfH = Height / 2; double lfXScale = 1.0; double lfYScale = 1.0; double lfAngle, lfRadius, lfRadMax; double nh, nw, tw; if (Width > Height) { lfYScale = (double)Width / (double)Height; } else if (Height > Width) { lfXScale = (double)Height / (double)Width; } lfRadMax = (double)qMax(Height, Width) / 2.0; double th = lfYScale * (double)(prm.h - nHalfH); for (int w = prm.start; runningFlag() && (w < prm.stop); ++w) { tw = lfXScale * (double)(w - nHalfW); if (prm.Type) { // now, we get the distance lfRadius = qSqrt(th * th + tw * tw); // we find the angle from the center lfAngle = qAtan2(tw, th); // now we find the exact position's x and y nh = lfRadius * (double) Height / lfRadMax; nw = lfAngle * (double) Width / (2 * M_PI); nw = (double)nHalfW + nw; } else { lfRadius = (double)(prm.h) * lfRadMax / (double)Height; lfAngle = (double)(w) * (2 * M_PI) / (double) Width; nw = (double)nHalfW - (lfRadius / lfXScale) * qSin(lfAngle); nh = (double)nHalfH - (lfRadius / lfYScale) * qCos(lfAngle); } setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias); } } /* Function to apply the Polar Coordinates effect backported from ImageProcesqSing version 2 * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * Type => if true Polar Coordinate to Polar else inverse. * Antialias => Smart blurring result. * * Theory => Similar to PolarCoordinates from Photoshop. We apply the polar * transformation in a proportional (Height and Width) radius. */ void DistortionFXFilter::polarCoordinates(DImg* orgImage, DImg* destImage, bool Type, bool AntiAlias) { int progress; QList vals = multithreadedSteps(orgImage->width()); QList > tasks; Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.Type = Type; prm.AntiAlias = AntiAlias; // main loop for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h) { for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.h = h; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::polarCoordinatesMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)h * 100.0) / orgImage->height()); if (progress % 5 == 0) { postProgress(progress); } } } void DistortionFXFilter::tileMultithreaded(const Args& prm) { int tx, ty, progress=0, oldProgress=0; for (int h = prm.start; runningFlag() && (h < prm.stop); h += prm.HSize) { for (int w = 0; runningFlag() && (w < (int)prm.orgImage->width()); w += prm.WSize) { d->lock2.lock(); tx = d->generator.number(-prm.Random / 2, prm.Random / 2); ty = d->generator.number(-prm.Random / 2, prm.Random / 2); d->lock2.unlock(); prm.destImage->bitBltImage(prm.orgImage, w, h, prm.WSize, prm.HSize, w + tx, h + ty); } // Update the progress bar in dialog. progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } } /* Function to apply the tile effect * * data => The image data in RGBA mode. * Width => Width of image. * Height => Height of image. * WSize => Tile Width * HSize => Tile Height * Random => Maximum random value * * Theory => Similar to Tile effect from Photoshop and very easy to * understand. We get a rectangular area uqSing WSize and HSize and * replace in a position with a random distance from the original * position. */ void DistortionFXFilter::tile(DImg* orgImage, DImg* destImage, int WSize, int HSize, int Random) { if (WSize < 1) { WSize = 1; } if (HSize < 1) { HSize = 1; } if (Random < 1) { Random = 1; } Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.WSize = WSize; prm.HSize = HSize; prm.Random = Random; d->generator.seed(d->randomSeed); QList vals = multithreadedSteps(orgImage->height()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &DistortionFXFilter::tileMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } /* This code is shared by six methods. Write value of pixel w|h in data to pixel nw|nh in pResBits. Antialias if requested. */ void DistortionFXFilter::setPixelFromOther(int Width, int Height, bool sixteenBit, int bytesDepth, uchar* data, uchar* pResBits, int w, int h, double nw, double nh, bool AntiAlias) { DColor color; int offset = getOffset(Width, w, h, bytesDepth); if (AntiAlias) { uchar* const ptr = pResBits + offset; if (sixteenBit) { unsigned short* ptr16 = reinterpret_cast(ptr); DPixelsAliasFilter().pixelAntiAliasing16(reinterpret_cast(data), Width, Height, nw, nh, ptr16 + 3, ptr16 + 2, ptr16 + 1, ptr16); } else { DPixelsAliasFilter().pixelAntiAliasing(data, Width, Height, nw, nh, ptr + 3, ptr + 2, ptr + 1, ptr); } } else { // we get the position adjusted int offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth); // read color color.setColor(data + offsetOther, sixteenBit); // write color to destination color.setPixel(pResBits + offset); } } FilterAction DistortionFXFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("antiAlias"), d->antiAlias); action.addParameter(QLatin1String("type"), d->effectType); action.addParameter(QLatin1String("iteration"), d->iteration); action.addParameter(QLatin1String("level"), d->level); if (d->effectType == Tile) { action.addParameter(QLatin1String("randomSeed"), d->randomSeed); } return action; } void DistortionFXFilter::readParameters(const FilterAction& action) { d->antiAlias = action.parameter(QLatin1String("antiAlias")).toBool(); d->effectType = action.parameter(QLatin1String("type")).toInt(); d->iteration = action.parameter(QLatin1String("iteration")).toInt(); d->level = action.parameter(QLatin1String("level")).toInt(); if (d->effectType == Tile) { d->randomSeed = action.parameter(QLatin1String("randomSeed")).toUInt(); } } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/embossfilter.cpp b/core/libs/dimg/filters/fx/embossfilter.cpp index 7a2d2b5682..49ff31fc11 100644 --- a/core/libs/dimg/filters/fx/embossfilter.cpp +++ b/core/libs/dimg/filters/fx/embossfilter.cpp @@ -1,201 +1,201 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Emboss threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * Original Emboss algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #include "embossfilter.h" // C++ includes #include #include // Qt includes #include #include // krazy:exclude=includes // KDE includes #include // Local includes #include "dimg.h" #include "digikam_globals.h" namespace Digikam { EmbossFilter::EmbossFilter(QObject* const parent) : DImgThreadedFilter(parent) { m_depth = 30; initFilter(); } EmbossFilter::EmbossFilter(DImg* const orgImage, QObject* const parent, int depth) : DImgThreadedFilter(orgImage, parent, QLatin1String("Emboss")) { m_depth = depth; initFilter(); } EmbossFilter::~EmbossFilter() { cancelFilter(); } QString EmbossFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Emboss Effect")); } /** Function to apply the EmbossFilter effect * This method have been ported from Pieter Z. Voloshyn algorithm code. * * Theory => This is an amazing effect. And the theory is very simple to * understand. You get the difference between the colors and * increase it. After this, get the gray tone */ void EmbossFilter::embossMultithreaded(uint start, uint stop, uint h, double Depth) { int Width = m_orgImage.width(); int Height = m_orgImage.height(); bool sixteenBit = m_orgImage.sixteenBit(); int bytesDepth = m_orgImage.bytesDepth(); uchar* const Bits = m_destImage.bits(); int red, green, blue, gray; DColor color, colorOther; int offset, offsetOther; for (uint w = start ; runningFlag() && (w < stop) ; ++w) { offset = getOffset(Width, w, h, bytesDepth); offsetOther = getOffset(Width, w + Lim_Max(w, 1, Width), h + Lim_Max(h, 1, Height), bytesDepth); color.setColor(Bits + offset, sixteenBit); colorOther.setColor(Bits + offsetOther, sixteenBit); if (sixteenBit) { red = abs((int)((color.red() - colorOther.red()) * Depth + 32768)); green = abs((int)((color.green() - colorOther.green()) * Depth + 32768)); blue = abs((int)((color.blue() - colorOther.blue()) * Depth + 32768)); gray = CLAMP065535((red + green + blue) / 3); } else { red = abs((int)((color.red() - colorOther.red()) * Depth + 128)); green = abs((int)((color.green() - colorOther.green()) * Depth + 128)); blue = abs((int)((color.blue() - colorOther.blue()) * Depth + 128)); gray = CLAMP0255((red + green + blue) / 3); } // Overwrite RGB values to destination. Alpha remains unchanged. color.setRed(gray); color.setGreen(gray); color.setBlue(gray); color.setPixel(Bits + offset); } } void EmbossFilter::filterImage() { // Initial copy memcpy(m_destImage.bits(), m_orgImage.bits(), m_destImage.numBytes()); double Depth = m_depth / 10.0; int progress; QList vals = multithreadedSteps(m_orgImage.width()); for (uint h = 0 ; runningFlag() && (h < m_orgImage.height()) ; ++h) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &EmbossFilter::embossMultithreaded, vals[j], vals[j+1], h, Depth )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); progress = (int)(((double)h * 100.0) / m_orgImage.height()); if (progress % 5 == 0) { postProgress(progress); } } } /** Function to limit the max and min values defined by the developer. */ int EmbossFilter::Lim_Max(int Now, int Up, int Max) { --Max; while (Now > Max - Up) { --Up; } return (Up); } int EmbossFilter::getOffset(int Width, int X, int Y, int bytesDepth) { return (Y * Width * bytesDepth) + (X * bytesDepth); } FilterAction EmbossFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("depth"), m_depth); return action; } void EmbossFilter::readParameters(const Digikam::FilterAction& action) { m_depth = action.parameter(QLatin1String("depth")).toInt(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/filmgrainfilter.cpp b/core/libs/dimg/filters/fx/filmgrainfilter.cpp index 6d9dd1966d..d09d171ab8 100644 --- a/core/libs/dimg/filters/fx/filmgrainfilter.cpp +++ b/core/libs/dimg/filters/fx/filmgrainfilter.cpp @@ -1,452 +1,452 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : filter to add Film Grain to image. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2005-2010 by Marcel Wiesweg * Copyright (C) 2010 by Julien Narboux * * 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, 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. * * ============================================================ */ #include "filmgrainfilter.h" // C++ includes #include #include // Qt includes #include // krazy:exclude=includes #include // KDE includes #include // Local includes #include "dimg.h" #include "digikam_globals.h" #include "randomnumbergenerator.h" namespace Digikam { class Q_DECL_HIDDEN FilmGrainFilter::Private { public: explicit Private() : div(0.0), leadLumaNoise(1.0), leadChromaBlueNoise(1.0), leadChromaRedNoise(1.0), globalProgress(0) { } enum YUVChannel { Luma = 0, ChromaBlue, ChromaRed }; double div; double leadLumaNoise; double leadChromaBlueNoise; double leadChromaRedNoise; FilmGrainContainer settings; RandomNumberGenerator generator; int globalProgress; QMutex lock; QMutex lock2; // RandomNumberGenerator is not re-entrant (dixit Boost lib) }; FilmGrainFilter::FilmGrainFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } FilmGrainFilter::FilmGrainFilter(DImg* const orgImage, QObject* const parent, const FilmGrainContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("FilmGrain")), d(new Private) { d->settings = settings; initFilter(); } FilmGrainFilter::FilmGrainFilter(DImgThreadedFilter* const parentFilter, const DImg& orgImage, const DImg& destImage, int progressBegin, int progressEnd, const FilmGrainContainer& settings) : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd, parentFilter->filterName() + QLatin1String(": FilmGrain")), d(new Private) { d->settings = settings; filterImage(); } FilmGrainFilter::~FilmGrainFilter() { cancelFilter(); delete d; } QString FilmGrainFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Film Grain Effect")); } void FilmGrainFilter::filmgrainMultithreaded(uint start, uint stop) { // To emulate grain size we use a matrix [grainSize x grainSize]. // We will parse whole image using grainSize step. Color from a reference point located // on the top left corner of matrix will be used to apply noise on whole matrix. // In first, for each matrix processed over the image, we compute the lead noise value // using Uniform noise generator. // In second time, all others points from the matrix are process to add supplemental noise // generated with Gaussian or Poisson noise generator. DColor refCol, matCol; uint progress=0, oldProgress=0, posX, posY; // Reference point noise adjustments. double refLumaNoise = 0.0, refLumaRange = 0.0; double refChromaBlueNoise = 0.0, refChromaBlueRange = 0.0; double refChromaRedNoise = 0.0, refChromaRedRange = 0.0; // Current matrix point noise adjustments. double matLumaNoise = 0.0, matLumaRange = 0.0; double matChromaBlueNoise = 0.0, matChromaBlueRange = 0.0; double matChromaRedNoise = 0.0, matChromaRedRange = 0.0; uint width = m_orgImage.width(); uint height = m_orgImage.height(); for (uint x = start ; runningFlag() && (x < stop) ; x += d->settings.grainSize) { for (uint y = 0; runningFlag() && y < height; y += d->settings.grainSize) { refCol = m_orgImage.getPixelColor(x, y); computeNoiseSettings(refCol, refLumaRange, refLumaNoise, refChromaBlueRange, refChromaBlueNoise, refChromaRedRange, refChromaRedNoise); // Grain size matrix processing. for (int zx = 0; runningFlag() && zx < d->settings.grainSize; ++zx) { for (int zy = 0; runningFlag() && zy < d->settings.grainSize; ++zy) { posX = x + zx; posY = y + zy; if (posX < width && posY < height) { matCol = m_orgImage.getPixelColor(posX, posY); computeNoiseSettings(matCol, matLumaRange, matLumaNoise, matChromaBlueRange, matChromaBlueNoise, matChromaRedRange, matChromaRedNoise); if (d->settings.addLuminanceNoise) { if (((refLumaRange - matLumaRange) / refLumaRange) > 0.1) { adjustYCbCr(matCol, matLumaRange, matLumaNoise, Private::Luma); } else { adjustYCbCr(matCol, refLumaRange, refLumaNoise, Private::Luma); } } if (d->settings.addChrominanceBlueNoise) { if (((refChromaBlueRange - matChromaBlueRange) / refChromaBlueRange) > 0.1) { adjustYCbCr(matCol, matChromaBlueRange, matChromaBlueNoise, Private::ChromaBlue); } else { adjustYCbCr(matCol, refChromaBlueRange, refChromaBlueNoise, Private::ChromaBlue); } } if (d->settings.addChrominanceRedNoise) { if (((refChromaRedRange - matChromaRedRange) / refChromaRedRange) > 0.1) { adjustYCbCr(matCol, matChromaRedRange, matChromaRedNoise, Private::ChromaBlue); } else { adjustYCbCr(matCol, refChromaRedRange, refChromaRedNoise, Private::ChromaRed); } } m_destImage.setPixelColor(posX, posY, matCol); } } } } progress = (int)( ( (double)x * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (stop-start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } } /** This method have been implemented following this report in bugzilla : https://bugs.kde.org/show_bug.cgi?id=148540 We use YCbCr color space to perform noise addition. Please follow this url for details about this color space : http://en.allexperts.com/e/y/yc/ycbcr.htm */ void FilmGrainFilter::filterImage() { if (d->settings.lumaIntensity <= 0 || d->settings.chromaBlueIntensity <= 0 || d->settings.chromaRedIntensity <= 0 || !d->settings.isDirty()) { m_destImage = m_orgImage; return; } d->div = m_orgImage.sixteenBit() ? 65535.0 : 255.0; d->leadLumaNoise = d->settings.lumaIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0); d->leadChromaBlueNoise = d->settings.chromaBlueIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0); d->leadChromaRedNoise = d->settings.chromaRedIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0); d->generator.seed(1); // noise will always be the same QList vals = multithreadedSteps(m_orgImage.width()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &FilmGrainFilter::filmgrainMultithreaded, vals[j], vals[j+1] )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } /** This method compute lead noise of reference matrix point used to simulate graininess size */ void FilmGrainFilter::computeNoiseSettings(const DColor& col, double& luRange, double& luNoise, double& cbRange, double& cbNoise, double& crRange, double& crNoise) { if (d->settings.addLuminanceNoise) { luRange = interpolate(d->settings.lumaShadows, d->settings.lumaMidtones, d->settings.lumaHighlights, col) * d->leadLumaNoise + 1.0; luNoise = randomizeUniform(luRange); } if (d->settings.addChrominanceBlueNoise) { cbRange = interpolate(d->settings.chromaBlueShadows, d->settings.chromaBlueMidtones, d->settings.chromaBlueHighlights, col) * d->leadChromaBlueNoise + 1.0; cbNoise = randomizeUniform(cbRange); } if (d->settings.addChrominanceRedNoise) { crRange = interpolate(d->settings.chromaRedShadows, d->settings.chromaRedMidtones, d->settings.chromaRedHighlights, col) * d->leadChromaRedNoise + 1.0; crNoise = randomizeUniform(crRange); } } /** This method apply grain adjustment on a pixel color channel from YCrCb color space. NRand is the lead uniform noise set from matrix used to scan whole image step by step. Additionally noise is applied on pixel using Poisson or Gaussian distribution. */ void FilmGrainFilter::adjustYCbCr(DColor& col, double range, double nRand, int channel) { double y, cb, cr, n2; col.getYCbCr(&y, &cb, &cr); if (d->settings.photoDistribution) { n2 = randomizePoisson((d->settings.grainSize / 2.0) * (range / 1.414)); } else { n2 = randomizeGauss((d->settings.grainSize / 2.0) * (range / 1.414)); } switch (channel) { case Private::Luma: y = CLAMP(y + (nRand + n2) / d->div, 0.0, 1.0); break; case Private::ChromaBlue: cb = CLAMP(cb + (nRand + n2) / d->div, 0.0, 1.0); break; default: // ChromaRed cr = CLAMP(cr + (nRand + n2) / d->div, 0.0, 1.0); break; } col.setYCbCr(y, cb, cr, col.sixteenBit()); } /** This method compute uniform noise value used to randomize matrix reference point. This value is lead noise apply to image. */ double FilmGrainFilter::randomizeUniform(double range) { d->lock2.lock(); double val = d->generator.number(- range / 2, range / 2); d->lock2.unlock(); return val; } /** This method compute Gaussian noise value used to randomize all matrix points. This value is added to lead noise value. */ double FilmGrainFilter::randomizeGauss(double sigma) { d->lock2.lock(); double u = - d->generator.number(-1.0, 0.0); // exclude 0 double v = d->generator.number(0.0, 1.0); d->lock2.unlock(); return (sigma * sqrt(-2 * log(u)) * cos(2 * M_PI * v)); } /** This method compute Poisson noise value used to randomize all matrix points. This value is added to lead noise value. Poisson noise is more realist to simulate photon noise apply on analog film. NOTE: see approximation of Poisson noise using Gauss algorithm from noise.c code take from : http://registry.gimp.org/node/13016 This method is very fast compared to real Poisson noise generator. */ double FilmGrainFilter::randomizePoisson(double lambda) { return (randomizeGauss(sqrt(lambda * d->settings.grainSize * d->settings.grainSize))); } /** This method interpolate gain adjustments to apply grain on shadows, midtones and highlights colors. The output value is a coefficient computed between 0.0 and 1.0. */ double FilmGrainFilter::interpolate(int shadows, int midtones, int highlights, const DColor& col) { double s = (shadows + 100.0) / 200.0; double m = (midtones + 100.0) / 200.0; double h = (highlights + 100.0) / 200.0; double y, cb, cr; col.getYCbCr(&y, &cb, &cr); if (y >= 0.0 && y <= 0.5) { return (s + 2 * (m - s) * y); } else if (y >= 0.5 && y <= 1.0) { return (2 * (h - m) * y + 2 * m - h); } else { return 1.0; } } FilterAction FilmGrainFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("grainSize"), d->settings.grainSize); action.addParameter(QLatin1String("photoDistribution"), d->settings.photoDistribution); action.addParameter(QLatin1String("addLuminanceNoise"), d->settings.addLuminanceNoise); action.addParameter(QLatin1String("lumaIntensity"), d->settings.lumaIntensity); action.addParameter(QLatin1String("lumaShadows"), d->settings.lumaShadows); action.addParameter(QLatin1String("lumaMidtones"), d->settings.lumaMidtones); action.addParameter(QLatin1String("lumaHighlights"), d->settings.lumaHighlights); action.addParameter(QLatin1String("addChrominanceBlueNoise"), d->settings.addChrominanceBlueNoise); action.addParameter(QLatin1String("chromaBlueIntensity"), d->settings.chromaBlueIntensity); action.addParameter(QLatin1String("chromaBlueShadows"), d->settings.chromaBlueShadows); action.addParameter(QLatin1String("chromaBlueMidtones"), d->settings.chromaBlueMidtones); action.addParameter(QLatin1String("chromaBlueHighlights"), d->settings.chromaBlueHighlights); action.addParameter(QLatin1String("addChrominanceRedNoise"), d->settings.addChrominanceRedNoise); action.addParameter(QLatin1String("chromaRedIntensity"), d->settings.chromaRedIntensity); action.addParameter(QLatin1String("chromaRedShadows"), d->settings.chromaRedShadows); action.addParameter(QLatin1String("chromaRedMidtones"), d->settings.chromaRedMidtones); action.addParameter(QLatin1String("chromaRedHighlights"), d->settings.chromaRedHighlights); return action; } void FilmGrainFilter::readParameters(const Digikam::FilterAction& action) { d->settings.grainSize = action.parameter(QLatin1String("grainSize")).toInt(); d->settings.photoDistribution = action.parameter(QLatin1String("photoDistribution")).toBool(); d->settings.addLuminanceNoise = action.parameter(QLatin1String("addLuminanceNoise")).toBool(); d->settings.lumaIntensity = action.parameter(QLatin1String("lumaIntensity")).toInt(); d->settings.lumaShadows = action.parameter(QLatin1String("lumaShadows")).toInt(); d->settings.lumaMidtones = action.parameter(QLatin1String("lumaMidtones")).toInt(); d->settings.lumaHighlights = action.parameter(QLatin1String("lumaHighlights")).toInt(); d->settings.addChrominanceBlueNoise = action.parameter(QLatin1String("addChrominanceBlueNoise")).toBool(); d->settings.chromaBlueIntensity = action.parameter(QLatin1String("chromaBlueIntensity")).toInt(); d->settings.chromaBlueShadows = action.parameter(QLatin1String("chromaBlueShadows")).toInt(); d->settings.chromaBlueMidtones = action.parameter(QLatin1String("chromaBlueMidtones")).toInt(); d->settings.chromaBlueHighlights = action.parameter(QLatin1String("chromaBlueHighlights")).toInt(); d->settings.addChrominanceRedNoise = action.parameter(QLatin1String("addChrominanceRedNoise")).toBool(); d->settings.chromaRedIntensity = action.parameter(QLatin1String("chromaRedIntensity")).toInt(); d->settings.chromaRedShadows = action.parameter(QLatin1String("chromaRedShadows")).toInt(); d->settings.chromaRedMidtones = action.parameter(QLatin1String("chromaRedMidtones")).toInt(); d->settings.chromaRedHighlights = action.parameter(QLatin1String("chromaRedHighlights")).toInt(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/oilpaintfilter.cpp b/core/libs/dimg/filters/fx/oilpaintfilter.cpp index 0a83101ff7..c3a3406edf 100644 --- a/core/libs/dimg/filters/fx/oilpaintfilter.cpp +++ b/core/libs/dimg/filters/fx/oilpaintfilter.cpp @@ -1,279 +1,279 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Oil Painting threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * Original OilPaint algorithm copyrighted 2004 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #include "oilpaintfilter.h" // C++ includes #include #include // Qt includes #include // krazy:exclude=includes #include // KDE includes #include // Local includes #include "dimg.h" namespace Digikam { class Q_DECL_HIDDEN OilPaintFilter::Private { public: explicit Private() : brushSize(1), smoothness(30), globalProgress(0) { } int brushSize; int smoothness; int globalProgress; QMutex lock; }; OilPaintFilter::OilPaintFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } OilPaintFilter::OilPaintFilter(DImg* const orgImage, QObject* const parent, int brushSize, int smoothness) : DImgThreadedFilter(orgImage, parent, QLatin1String("OilPaintFilter")), d(new Private) { d->brushSize = brushSize; d->smoothness = smoothness; initFilter(); } OilPaintFilter::~OilPaintFilter() { cancelFilter(); delete d; } QString OilPaintFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Oil Painter Effect")); } /** Function to apply the OilPaintFilter effect. * This method have been ported from Pieter Z. Voloshyn algorithm code. * * Theory: Using MostFrequentColor function we take the main color in * a matrix and simply write at the original position. */ void OilPaintFilter::oilPaintImageMultithreaded(uint start, uint stop) { QScopedPointer intensityCount(new uchar[d->smoothness + 1]); QScopedPointer averageColorR(new uint[d->smoothness + 1]); QScopedPointer averageColorG(new uint[d->smoothness + 1]); QScopedPointer averageColorB(new uint[d->smoothness + 1]); memset(intensityCount.data(), 0, sizeof(uchar)*(d->smoothness + 1)); memset(averageColorR.data(), 0, sizeof(uint)*(d->smoothness + 1)); memset(averageColorG.data(), 0, sizeof(uint)*(d->smoothness + 1)); memset(averageColorB.data(), 0, sizeof(uint)*(d->smoothness + 1)); int oldProgress=0, progress=0; DColor mostFrequentColor; mostFrequentColor.setSixteenBit(m_orgImage.sixteenBit()); uchar* dest = m_destImage.bits(); uchar* dptr = nullptr; for (uint h2 = start; runningFlag() && (h2 < stop); ++h2) { for (uint w2 = 0; runningFlag() && (w2 < m_orgImage.width()); ++w2) { mostFrequentColor = MostFrequentColor(m_orgImage, w2, h2, d->brushSize, d->smoothness, intensityCount.data(), averageColorR.data(), averageColorG.data(), averageColorB.data()); dptr = dest + w2 * m_orgImage.bytesDepth() + (m_orgImage.width() * h2 * m_orgImage.bytesDepth()); mostFrequentColor.setPixel(dptr); } progress = (int)( ( (double)h2 * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (stop-start)); if ((progress % 5 == 0) && (progress > oldProgress)) { d->lock.lock(); oldProgress = progress; d->globalProgress += 5; postProgress(d->globalProgress); d->lock.unlock(); } } } void OilPaintFilter::filterImage() { QList vals = multithreadedSteps(m_orgImage.height()); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &OilPaintFilter::oilPaintImageMultithreaded, vals[j], vals[j+1] )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } /** Function to determine the most frequent color in a matrix * * Bits => Bits array * Width => Image width * Height => Image height * X => Position horizontal * Y => Position vertical * Radius => Is the radius of the matrix to be analyzed * Intensity => Intensity to calculate * * Theory => This function creates a matrix with the analyzed pixel in * the center of this matrix and find the most frequently color */ DColor OilPaintFilter::MostFrequentColor(DImg& src, int X, int Y, int Radius, int Intensity, uchar* intensityCount, uint* averageColorR, uint* averageColorG, uint* averageColorB) { int i, w, h, I, Width, Height; uint red, green, blue; uchar* dest = src.bits(); int bytesDepth = src.bytesDepth(); uchar* sptr = nullptr; bool sixteenBit = src.sixteenBit(); DColor mostFrequentColor; double Scale = Intensity / (sixteenBit ? 65535.0 : 255.0); Width = (int)src.width(); Height = (int)src.height(); // Erase the array memset(intensityCount, 0, (Intensity + 1) * sizeof(uchar)); for (w = X - Radius; w <= X + Radius; ++w) { for (h = Y - Radius; h <= Y + Radius; ++h) { // This condition helps to identify when a point doesn't exist if ((w >= 0) && (w < Width) && (h >= 0) && (h < Height)) { sptr = dest + w * bytesDepth + (Width * h * bytesDepth); DColor color(sptr, sixteenBit); red = (uint)color.red(); green = (uint)color.green(); blue = (uint)color.blue(); I = lround(GetIntensity(red, green, blue) * Scale); intensityCount[I]++; if (intensityCount[I] == 1) { averageColorR[I] = red; averageColorG[I] = green; averageColorB[I] = blue; } else { averageColorR[I] += red; averageColorG[I] += green; averageColorB[I] += blue; } } } } I = 0; int MaxInstance = 1; for (i = 0 ; i <= Intensity ; ++i) { if (intensityCount[i] > MaxInstance) { I = i; MaxInstance = intensityCount[i]; } } // get Alpha channel value from original (unchanged) mostFrequentColor = src.getPixelColor(X, Y); // Overwrite RGB values to destination. mostFrequentColor.setRed(averageColorR[I] / MaxInstance); mostFrequentColor.setGreen(averageColorG[I] / MaxInstance); mostFrequentColor.setBlue(averageColorB[I] / MaxInstance); return mostFrequentColor; } /** Function to calculate the color intensity and return the luminance (Y) * component of YIQ color model. */ double OilPaintFilter::GetIntensity(uint Red, uint Green, uint Blue) { return Red * 0.3 + Green * 0.59 + Blue * 0.11; } FilterAction OilPaintFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("brushSize"), d->brushSize); action.addParameter(QLatin1String("smoothness"), d->smoothness); return action; } void OilPaintFilter::readParameters(const Digikam::FilterAction& action) { d->brushSize = action.parameter(QLatin1String("brushSize")).toInt(); d->smoothness = action.parameter(QLatin1String("smoothness")).toInt(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/fx/raindropfilter.cpp b/core/libs/dimg/filters/fx/raindropfilter.cpp index bdf5371582..3e00f4bd1c 100644 --- a/core/libs/dimg/filters/fx/raindropfilter.cpp +++ b/core/libs/dimg/filters/fx/raindropfilter.cpp @@ -1,629 +1,629 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Raindrop threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * Original RainDrop algorithm copyrighted 2004-2005 by * Pieter Z. Voloshyn . * * 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, 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. * * ============================================================ */ #include "raindropfilter.h" // C++ includes #include // Qt includes #include #include #include #include // krazy:exclude=includes // KDE includes #include // Local includes #include "dimg.h" namespace Digikam { class Q_DECL_HIDDEN RainDropFilter::Private { public: explicit Private() : drop(80), amount(150), coeff(30), selection(QRect(0, 0, 0, 0)) { } int drop; int amount; int coeff; QRect selection; RandomNumberGenerator generator; QMutex lock; // RandomNumberGenerator is not re-entrant (dixit Boost lib) }; RainDropFilter::RainDropFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } RainDropFilter::RainDropFilter(DImg* const orgImage, QObject* const parent, int drop, int amount, int coeff, const QRect& selection) : DImgThreadedFilter(orgImage, parent, QLatin1String("RainDrop")), d(new Private) { d->drop = drop; d->amount = amount; d->coeff = coeff; d->selection = selection; d->generator.seedByTime(); initFilter(); } RainDropFilter::~RainDropFilter() { cancelFilter(); delete d; } QString RainDropFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Rain Drops Effect")); } void RainDropFilter::filterImage() { int w = m_orgImage.width(); int h = m_orgImage.height(); d->generator.reseed(); // If we have a region selection in image, use it to apply the filter modification around, // else, applied the filter on the full image. if (!d->selection.size().isNull()) { DImg selectedImg = m_orgImage.copy(d->selection); // Cut the original image in 4 areas without clipping region. DImg zone1 = m_orgImage.copy(0, 0, d->selection.x(), h); DImg zone2 = m_orgImage.copy(d->selection.x(), 0, d->selection.x() + d->selection.width(), d->selection.y()); DImg zone3 = m_orgImage.copy(d->selection.x(), d->selection.y() + d->selection.height(), d->selection.x() + d->selection.width(), h); DImg zone4 = m_orgImage.copy(d->selection.x() + d->selection.width(), 0, w, h); DImg zone1Dest = DImg(zone1.width(), zone1.height(), zone1.sixteenBit(), zone1.hasAlpha()); DImg zone2Dest = DImg(zone2.width(), zone2.height(), zone2.sixteenBit(), zone2.hasAlpha()); DImg zone3Dest = DImg(zone3.width(), zone3.height(), zone3.sixteenBit(), zone3.hasAlpha()); DImg zone4Dest = DImg(zone4.width(), zone4.height(), zone4.sixteenBit(), zone4.hasAlpha()); // Apply effect on each area. rainDropsImage(&zone1, &zone1Dest, 0, d->drop, d->amount, d->coeff, true, 0, 25); rainDropsImage(&zone2, &zone2Dest, 0, d->drop, d->amount, d->coeff, true, 25, 50); rainDropsImage(&zone3, &zone3Dest, 0, d->drop, d->amount, d->coeff, true, 50, 75); rainDropsImage(&zone4, &zone4Dest, 0, d->drop, d->amount, d->coeff, true, 75, 100); // Build the target image. m_destImage.bitBltImage(&zone1Dest, 0, 0); m_destImage.bitBltImage(&zone2Dest, d->selection.x(), 0); m_destImage.bitBltImage(&zone3Dest, d->selection.x(), d->selection.y() + d->selection.height()); m_destImage.bitBltImage(&zone4Dest, d->selection.x() + d->selection.width(), 0); m_destImage.bitBltImage(&selectedImg, d->selection.x(), d->selection.y()); } else { rainDropsImage(&m_orgImage, &m_destImage, 0, d->drop, d->amount, d->coeff, true, 0, 100); } } void RainDropFilter::rainDropsImageMultithreaded(const Args& prm) { int nRandSize; int nRandX, nRandY; bool bResp = false; int nWidth = prm.orgImage->width(); int nHeight = prm.orgImage->height(); bool sixteenBit = prm.orgImage->sixteenBit(); int bytesDepth = prm.orgImage->bytesDepth(); uchar* data = prm.orgImage->bits(); uchar* pResBits = prm.destImage->bits(); for (uint nCounter = prm.start ; runningFlag() && (bResp == false) && (nCounter < prm.stop) ; ++nCounter) { d->lock.lock(); nRandX = d->generator.number(0, nWidth - 1); nRandY = d->generator.number(0, nHeight - 1); nRandSize = d->generator.number(prm.MinDropSize, prm.MaxDropSize); d->lock.unlock(); bResp = CreateRainDrop(data, nWidth, nHeight, sixteenBit, bytesDepth, pResBits, prm.pStatusBits, nRandX, nRandY, nRandSize, prm.Coeff, prm.bLimitRange); } } /* Function to apply the RainDrops effect backported from ImageProcessing version 2 * * orgImage => The image * MinDropSize => It's the minimum random size for rain drop. * MaxDropSize => It's the minimum random size for rain drop. * Amount => It's the maximum number for rain drops inside the image. * Coeff => It's the fisheye's coefficient. * bLimitRange => If true, the drop will not be cut. * progressMin => Min. value for progress bar (can be different if using clipping area). * progressMax => Max. value for progress bar (can be different if using clipping area). * * Theory => This functions does several math's functions and the engine * is simple to undestand, but a little hard to implement. A * control will indicate if there is or not a raindrop in that * area, if not, a fisheye effect with a random size (max=MaxDropSize) * will be applied, after this, a shadow will be applied too. * and after this, a blur function will finish the effect. */ void RainDropFilter::rainDropsImage(DImg* const orgImage, DImg* const destImage, int MinDropSize, int MaxDropSize, int Amount, int Coeff, bool bLimitRange, int progressMin, int progressMax) { if (Amount <= 0) { return; } if (MinDropSize >= MaxDropSize) { MaxDropSize = MinDropSize + 1; } if (MaxDropSize <= 0) { return; } QScopedArrayPointer pStatusBits(new uchar[orgImage->height() * orgImage->width()]); memset(pStatusBits.data(), 0, orgImage->height() * orgImage->width() * sizeof(uchar)); // Initially, copy all pixels to destination destImage->bitBltImage(orgImage, 0, 0); // Randomize. QList vals = multithreadedSteps(10000); Args prm; prm.orgImage = orgImage; prm.destImage = destImage; prm.MinDropSize = MinDropSize; prm.MaxDropSize = MaxDropSize; prm.Coeff = Coeff; prm.bLimitRange = bLimitRange; prm.pStatusBits = pStatusBits.data(); for (int i = 0 ; runningFlag() && (i < Amount) ; ++i) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &RainDropFilter::rainDropsImageMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); postProgress((int)(progressMin + ((double)(i) * (double)(progressMax - progressMin)) / (double)Amount)); } } bool RainDropFilter::CreateRainDrop(uchar* const pBits, int Width, int Height, bool sixteenBit, int bytesDepth, uchar* const pResBits, uchar* const pStatusBits, int X, int Y, int DropSize, double Coeff, bool bLimitRange) { if (CanBeDropped(Width, Height, pStatusBits, X, Y, DropSize, bLimitRange)) { int w, h, nw1, nh1, nw2, nh2; int nBright; double lfRadius, lfOldRadius, lfAngle; DColor imageData; uint nTotalR, nTotalG, nTotalB, offset; int nBlurPixels, nBlurRadius; Coeff *= 0.01; int nHalfSize = DropSize / 2; double lfDiv = (double)nHalfSize / log1p(Coeff * (double)nHalfSize); for (h = -nHalfSize ; runningFlag() && (h <= nHalfSize) ; ++h) { for (w = -nHalfSize ; runningFlag() && (w <= nHalfSize) ; ++w) { lfRadius = qSqrt(h * h + w * w); lfAngle = qAtan2((double)h, (double)w); if (lfRadius <= (double)nHalfSize) { lfOldRadius = lfRadius; lfRadius = (qExp(lfRadius / lfDiv) - 1.0) / Coeff; nw1 = (int)((double)X + lfRadius * qCos(lfAngle)); nh1 = (int)((double)Y + lfRadius * qSin(lfAngle)); nw2 = X + w; nh2 = Y + h; if (isInside(Width, Height, nw1, nh1)) { if (isInside(Width, Height, nw2, nh2)) { nBright = 0; if (lfOldRadius >= 0.9 * (double)nHalfSize) { if ((lfAngle >= 0.0) && (lfAngle < 2.25)) { nBright = -80; } else if ((lfAngle >= 2.25) && (lfAngle < 2.5)) { nBright = -40; } else if ((lfAngle >= -0.25) && (lfAngle < 0.0)) { nBright = -40; } } else if (lfOldRadius >= 0.8 * (double)nHalfSize) { if ((lfAngle >= 0.75) && (lfAngle < 1.50)) { nBright = -40; } else if ((lfAngle >= -0.10) && (lfAngle < 0.75)) { nBright = -30; } else if ((lfAngle >= 1.50) && (lfAngle < 2.35)) { nBright = -30; } } else if (lfOldRadius >= 0.7 * (double)nHalfSize) { if ((lfAngle >= 0.10) && (lfAngle < 2.0)) { nBright = -20; } else if ((lfAngle >= -2.50) && (lfAngle < -1.90)) { nBright = 60; } } else if (lfOldRadius >= 0.6 * (double)nHalfSize) { if ((lfAngle >= 0.50) && (lfAngle < 1.75)) { nBright = -20; } else if ((lfAngle >= 0.0) && (lfAngle < 0.25)) { nBright = 20; } else if ((lfAngle >= 2.0) && (lfAngle < 2.25)) { nBright = 20; } } else if (lfOldRadius >= 0.5 * (double)nHalfSize) { if ((lfAngle >= 0.25) && (lfAngle < 0.50)) { nBright = 30; } else if ((lfAngle >= 1.75) && (lfAngle < 2.0)) { nBright = 30; } } else if (lfOldRadius >= 0.4 * (double)nHalfSize) { if ((lfAngle >= 0.5) && (lfAngle < 1.75)) { nBright = 40; } } else if (lfOldRadius >= 0.3 * (double)nHalfSize) { if ((lfAngle >= 0.0) && (lfAngle < 2.25)) { nBright = 30; } } else if (lfOldRadius >= 0.2 * (double)nHalfSize) { if ((lfAngle >= 0.5) && (lfAngle < 1.75)) { nBright = 20; } } imageData.setColor(pBits + pixelOffset(Width, nw1, nh1, bytesDepth), sixteenBit); if (sixteenBit) { // convert difference to 16-bit range if (nBright > 0) { nBright = (nBright + 1) * 256 - 1; } else { nBright = (nBright - 1) * 256 + 1; } imageData.setRed(limitValues16(imageData.red() + nBright)); imageData.setGreen(limitValues16(imageData.green() + nBright)); imageData.setBlue(limitValues16(imageData.blue() + nBright)); } else { imageData.setRed(limitValues8(imageData.red() + nBright)); imageData.setGreen(limitValues8(imageData.green() + nBright)); imageData.setBlue(limitValues8(imageData.blue() + nBright)); } imageData.setPixel(pResBits + pixelOffset(Width, nw2, nh2, bytesDepth)); } } } } } nBlurRadius = DropSize / 25 + 1; for (h = -nHalfSize - nBlurRadius ; runningFlag() && (h <= nHalfSize + nBlurRadius) ; ++h) { for (w = -nHalfSize - nBlurRadius ; runningFlag() && (w <= nHalfSize + nBlurRadius) ; ++w) { lfRadius = qSqrt(h * h + w * w); if (lfRadius <= (double)nHalfSize * 1.1) { nTotalR = nTotalG = nTotalB = 0; nBlurPixels = 0; for (nh1 = -nBlurRadius ; runningFlag() && (nh1 <= nBlurRadius) ; ++nh1) { for (nw1 = -nBlurRadius ; runningFlag() && (nw1 <= nBlurRadius) ; ++nw1) { nw2 = X + w + nw1; nh2 = Y + h + nh1; if (isInside(Width, Height, nw2, nh2)) { imageData.setColor(pResBits + pixelOffset(Width, nw2, nh2, bytesDepth), sixteenBit); nTotalR += imageData.red(); nTotalG += imageData.green(); nTotalB += imageData.blue(); ++nBlurPixels; } } } nw1 = X + w; nh1 = Y + h; if (isInside(Width, Height, nw1, nh1) && (nBlurPixels != 0)) { offset = pixelOffset(Width, nw1, nh1, bytesDepth); // to preserve alpha channel imageData.setColor(pResBits + offset, sixteenBit); imageData.setRed(nTotalR / nBlurPixels); imageData.setGreen(nTotalG / nBlurPixels); imageData.setBlue(nTotalB / nBlurPixels); imageData.setPixel(pResBits + offset); } } } } SetDropStatusBits(Width, Height, pStatusBits, X, Y, DropSize); } else { return false; } return true; } bool RainDropFilter::CanBeDropped(int Width, int Height, uchar* const pStatusBits, int X, int Y, int DropSize, bool bLimitRange) { int w, h, i = 0; int nHalfSize = DropSize / 2; if (!pStatusBits) { return true; } for (h = Y - nHalfSize ; h <= Y + nHalfSize ; ++h) { for (w = X - nHalfSize ; w <= X + nHalfSize ; ++w) { if (isInside(Width, Height, w, h)) { i = h * Width + w; if (pStatusBits[i]) { return false; } } else { if (bLimitRange) { return false; } } } } return true; } bool RainDropFilter::SetDropStatusBits(int Width, int Height, uchar* const pStatusBits, int X, int Y, int DropSize) { int w, h, i = 0; int nHalfSize = DropSize / 2; if (!pStatusBits) { return false; } for (h = Y - nHalfSize ; h <= Y + nHalfSize ; ++h) { for (w = X - nHalfSize ; w <= X + nHalfSize ; ++w) { if (isInside(Width, Height, w, h)) { i = h * Width + w; pStatusBits[i] = 255; } } } return true; } FilterAction RainDropFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("amount"), d->amount); action.addParameter(QLatin1String("coeff"), d->coeff); action.addParameter(QLatin1String("drop"), d->drop); action.addParameter(QLatin1String("selectedH"), d->selection.height()); action.addParameter(QLatin1String("selectedW"), d->selection.width()); action.addParameter(QLatin1String("selectedX"), d->selection.x()); action.addParameter(QLatin1String("selectedY"), d->selection.y()); action.addParameter(QLatin1String("randomSeed"), d->generator.currentSeed()); return action; } void RainDropFilter::readParameters(const FilterAction& action) { int x=0, y=0, w=0, h=0; d->amount = action.parameter(QLatin1String("amount")).toInt(); d->coeff = action.parameter(QLatin1String("coeff")).toInt(); d->drop = action.parameter(QLatin1String("drop")).toInt(); h = action.parameter(QLatin1String("selectedH")).toInt(); w = action.parameter(QLatin1String("selectedW")).toInt(); x = action.parameter(QLatin1String("selectedX")).toInt(); y = action.parameter(QLatin1String("selectedY")).toInt(); d->selection = QRect(x, y, w, h); d->generator.seed(action.parameter(QLatin1String("randomSeed")).toUInt()); } int RainDropFilter::limitValues8(int ColorValue) { if (ColorValue > 255) { ColorValue = 255; } if (ColorValue < 0) { ColorValue = 0; } return ColorValue; } int RainDropFilter::limitValues16(int ColorValue) { if (ColorValue > 65535) { ColorValue = 65535; } if (ColorValue < 0) { ColorValue = 0; } return ColorValue; } bool RainDropFilter::isInside (int Width, int Height, int X, int Y) { bool bIsWOk = ((X < 0) ? false : (X >= Width ) ? false : true); bool bIsHOk = ((Y < 0) ? false : (Y >= Height) ? false : true); return (bIsWOk && bIsHOk); } int RainDropFilter::pixelOffset(int Width, int X, int Y, int bytesDepth) { return (Y * Width * bytesDepth + X * bytesDepth); } } // namespace Digikam diff --git a/core/libs/dimg/filters/greycstoration/cimg/greycstoration.h b/core/libs/dimg/filters/greycstoration/cimg/greycstoration.h index 700ec5e69c..371edb0685 100644 --- a/core/libs/dimg/filters/greycstoration/cimg/greycstoration.h +++ b/core/libs/dimg/filters/greycstoration/cimg/greycstoration.h @@ -1,500 +1,500 @@ /* # # File : greycstoration.h # ( C++ header file - CImg plug-in ) # # Description : GREYCstoration plug-in allowing easy integration in # third parties softwares. # ( http://www.greyc.ensicaen.fr/~dtschump/greycstoration/ ) # This file is a part of the CImg Library project. # ( http://cimg.sourceforge.net ) # # THIS PLUG-IN IS INTENDED FOR DEVELOPERS ONLY. IT EASES THE INTEGRATION ALGORITHM IN # THIRD PARTIES SOFTWARES. IF YOU ARE A USER OF GREYCSTORATION, PLEASE LOOK # AT THE FILE 'greycstoration.cpp' WHICH IS THE SOURCE OF THE COMPLETE # COMMAND LINE GREYCSTORATION TOOL. # # Copyright : David Tschumperle # ( http://www.greyc.ensicaen.fr/~dtschump/ ) # # License : CeCILL v2.0 # ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ) # # This software is governed by the CeCILL license under French law and # abiding by the rules of distribution of free software. You can use, # modify and/ or redistribute the software under the terms of the CeCILL # license as circulated by CEA, CNRS and INRIA at the following URL # "http://www.cecill.info". # # As a counterpart to the access to the source code and rights to copy, # modify and redistribute granted by the license, users are provided only # with a limited warranty and the software's author, the holder of the # economic rights, and the successive licensors have only limited # liability. # # In this respect, the user's attention is drawn to the risks associated # with loading, using, modifying and/or developing or reproducing the # software by the user in light of its specific status of free software, # that may mean that it is complicated to manipulate, and that also # therefore means that it is reserved for developers and experienced # professionals having in-depth computer knowledge. Users are therefore # encouraged to load and test the software's suitability as regards their # requirements in conditions enabling the security of their systems and/or # data to be ensured and, more generally, to use and operate it in the # same conditions as regards security. # # The fact that you are presently reading this means that you have had # knowledge of the CeCILL license and that you accept its terms. # */ #ifndef cimg_plugin_greycstoration #define cimg_plugin_greycstoration // NOTE: You need to include // #include // #include // // #include "dynamicthread.h" class GreycstorationParameters { public: // Tell if the patch-based algorithm is selected bool patch_based; // Parameters specific to the non-patch regularization algorithm float amplitude; float sharpness; float anisotropy; float alpha; float sigma; float gfact; float dl; float da; float gauss_prec; unsigned int interpolation; // Parameters specific to the patch-based regularization algorithm unsigned int patch_size; float sigma_s; float sigma_p; unsigned int lookup_size; // Non-specific parameters of the algorithms. unsigned int tile; unsigned int tile_border; bool fast_approx; // Default constructor GreycstorationParameters() : patch_based(false), amplitude(0), sharpness(0), anisotropy(0), alpha(0), sigma(0), gfact(1), dl(0), da(0), gauss_prec(0), interpolation(0), patch_size(0), sigma_s(0), sigma_p(0), lookup_size(0), tile(0), tile_border(0), fast_approx(false) {} }; class GreycstorationThreadManager; // class just to have a constructor to initialize the variable class GreycstorationThreadManagerContainer { public: GreycstorationThreadManagerContainer() : threadManager(0) {} GreycstorationThreadManager* threadManager; }; GreycstorationThreadManagerContainer threadManagerContainer; void setThreadManager(GreycstorationThreadManager* threadManager) { threadManagerContainer.threadManager = threadManager; } void resetThreadManager() { setThreadManager(0); } class GreycstorationWorkingThread : public Digikam::DynamicThread { public: GreycstorationWorkingThread(GreycstorationThreadManager* manager, int threadNumber) : manager(manager), threadNumber(threadNumber) { } virtual void run() override { manager->workerMethod(threadNumber); } GreycstorationThreadManager* const manager; const int threadNumber; }; //template class GreycstorationThreadManager { public: GreycstorationThreadManager() : counter(0), stopRequest(false), activeThreads(0), source(0), temporary(0), empty_mask(new CImg()) { } ~GreycstorationThreadManager() { stop(); wait(); finish(); qDeleteAll(threads); delete temporary; delete empty_mask; } //! Run the non-patch version of the GREYCstoration algorithm on the instance image, using a mask. void start(CImg& s, const CImg* m, const float amplitude=60, const float sharpness=0.7f, const float anisotropy=0.3f, const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f, const float dl=0.8f, const float da=30.0f, const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true, const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { GreycstorationParameters params; params.patch_based = false; params.amplitude = amplitude; params.sharpness = sharpness; params.anisotropy = anisotropy; params.alpha = alpha; params.sigma = sigma; params.gfact = gfact; params.dl = dl; params.da = da; params.gauss_prec = gauss_prec; params.interpolation = interpolation; params.fast_approx = fast_approx; params.tile_border = tile_border; mask = m; start(&s, params, tile, nb_threads); } //! Run the non-patch version of the GREYCstoration algorithm on the instance image. void start(CImg& s, const float amplitude=50, const float sharpness=0.7f, const float anisotropy=0.3f, const float alpha=0.6f, const float sigma=1.1f, const float gfact=1.0f, const float dl=0.8f, const float da=30.0f, const float gauss_prec=2.0f, const unsigned int interpolation=0, const bool fast_approx=true, const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { return start(s, empty_mask, amplitude,sharpness,anisotropy,alpha,sigma,gfact,dl,da,gauss_prec, interpolation,fast_approx,tile,tile_border,nb_threads); } //! Run the patch-based version of the GREYCstoration algorithm on the instance image. void start(CImg& s, const unsigned int patch_size=5, const float sigma_p=10, const float sigma_s=100, const unsigned int lookup_size=20, const bool fast_approx=true, const unsigned int tile=0, const unsigned int tile_border=0, const unsigned int nb_threads=1) { GreycstorationParameters params; params.patch_based = true; params.patch_size = patch_size; params.sigma_s = sigma_s; params.sigma_p = sigma_p; params.lookup_size = lookup_size; params.mask = empty_mask; params.tile_border = tile_border; params.fast_approx = fast_approx; start(&s, params, tile, nb_threads); } bool isRunning() const { - foreach(GreycstorationWorkingThread* thread, threads) + foreach (GreycstorationWorkingThread* thread, threads) { if (thread->isRunning()) { return true; } } return false; } void wait() { - foreach(GreycstorationWorkingThread* thread, threads) + foreach (GreycstorationWorkingThread* thread, threads) { thread->wait(); } } void finish() { if (temporary) { (*source) = (*temporary); } if (source) { source->resetThreadManager(); } } void stop() { - foreach(GreycstorationWorkingThread* thread, threads) + foreach (GreycstorationWorkingThread* thread, threads) { thread->stop(); } stopRequest = true; } float progress() const { const float da = p.da, factor = p.patch_based ? 1 : (1+360/da); float maxcounter = 0; if (p.tile==0) { maxcounter = source->width*source->height*source->depth*factor; } else { const unsigned int t = p.tile, b = p.tile_border, n = (1+(source->width-1)/t)*(1+(source->height-1)/t)*(1+(source->depth-1)/t); maxcounter = (source->width*source->height*source->depth + n*4*b*(b + t))*factor; } return cimg::min(counter*99.9f/maxcounter,99.9f); } // Waits at most the specified number of milliseconds, then returns current progress float waitABit(unsigned int msecs) { QMutexLocker lock(&mutex); if (activeThreads) { condVar.wait(&mutex, msecs); } return progress(); } protected: friend struct CImg; QMutex mutex; QWaitCondition condVar; volatile int counter; volatile bool stopRequest; volatile int activeThreads; GreycstorationParameters p; CImg* source; const CImg *mask; CImg *temporary; CImg *empty_mask; QList threads; void start(CImg* s, const GreycstorationParameters& params, unsigned int tile, unsigned int numberOfThreads) { /*if (!mask.is_empty() && !mask.is_sameXY(*this)) { throw CImgArgumentException("CImg<%s>::greycstoration_run() : Given mask (%u,%u,%u,%u,%p) and instance image " "(%u,%u,%u,%u,%p) have different dimensions.", pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data,width,height,depth,dim,data); }*/ if (numberOfThreads>16) { cimg::warn("CImg<%s>::greycstoration_run() : Multi-threading mode limited to 16 threads max."); } source = s; s->setThreadManager(this); p = params; p.tile = (tile && (tilewidth || tileheight || (source->depth>1 && tiledepth)))?tile:0, numberOfThreads = p.tile ? cimg::min(numberOfThreads,16U) : cimg::min(numberOfThreads,1U); counter = 0; stopRequest = false; activeThreads = 0; delete temporary; temporary = p.tile ? new CImg(*source) : 0; if (numberOfThreads) { for (int k=0; k<(int)numberOfThreads; k++) { if (threads.size() == k) { GreycstorationWorkingThread* thread = new GreycstorationWorkingThread(this, k); ++activeThreads; thread->start(); threads << thread; } else { threads[k]->start(); } } } else { // direct execution ++activeThreads; workerMethod(0); } } friend class GreycstorationWorkingThread; void workerMethod(unsigned int threadIndex) { QMutexLocker lock(&mutex); if (!p.tile) { // Non-tiled version //------------------ if (p.patch_based) { source->blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); } else { source->blur_anisotropic(*mask,p.amplitude,p.sharpness,p.anisotropy,p.alpha,p.sigma,p.dl,p.da,p.gauss_prec, p.interpolation,p.fast_approx,p.gfact); } } else { // Tiled version //--------------- const bool threed = (source->depth>1); const unsigned int b = p.tile_border; unsigned int ctile = 0; if (threed) { for (unsigned int z=0; zdepth && !stopRequest; z+=p.tile) { for (unsigned int y=0; yheight && !stopRequest; y+=p.tile) { for (unsigned int x=0; xwidth && !stopRequest; x+=p.tile) { if (threads.isEmpty() || ((ctile++)%threads.size())==(unsigned int)threadIndex) { const unsigned int x1 = x+p.tile-1, y1 = y+p.tile-1, z1 = z+p.tile-1, xe = x1width?x1:source->width-1, ye = y1height?y1:source->height-1, ze = z1depth?z1:source->depth-1; CImg img = source->get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true); CImg mask_tile = mask->is_empty() ? *mask : mask->get_crop(x-b,y-b,z-b,xe+b,ye+b,ze+b,true); //??img.greycstoration_params[0] = p; lock.unlock(); if (p.patch_based) { img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); } else { img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy, p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact); } lock.relock(); temporary->draw_image(x,y,z,img.crop(b,b,b,img.width-b,img.height-b,img.depth-b)); } } } } } else { for (unsigned int y=0; yheight && !stopRequest; y+=p.tile) { for (unsigned int x=0; xwidth && !stopRequest; x+=p.tile) { if (threads.isEmpty() || ((ctile++)%threads.size())==(unsigned int)threadIndex) { const unsigned int x1 = x+p.tile-1, y1 = y+p.tile-1, xe = x1width?x1:source->width-1, ye = y1height?y1:source->height-1; CImg img = source->get_crop(x-b,y-b,xe+b,ye+b,true); CImg mask_tile = mask->is_empty() ? *mask : mask->get_crop(x-b,y-b,xe+b,ye+b,true); //img.greycstoration_params[0] = p; lock.unlock(); if (p.patch_based) { img.blur_patch(p.patch_size,p.sigma_p,p.sigma_s,p.lookup_size,p.fast_approx); } else { img.blur_anisotropic(mask_tile,p.amplitude,p.sharpness,p.anisotropy, p.alpha,p.sigma,p.dl,p.da,p.gauss_prec,p.interpolation,p.fast_approx,p.gfact); } lock.relock(); temporary->draw_image(x,y,img.crop(b,b,img.width-b,img.height-b)); } } } } } --activeThreads; condVar.wakeAll(); } }; #define cimg_plugin_greycstoration_count \ if (threadManagerContainer.threadManager)\ { if (threadManagerContainer.threadManager->stopRequest) \ return *this; \ ++threadManagerContainer.threadManager->counter; } #define cimg_plugin_greycstoration_lock \ if (threadManagerContainer.threadManager) threadManagerContainer.threadManager->mutex.lock(); #define cimg_plugin_greycstoration_unlock \ if (threadManagerContainer.threadManager) threadManagerContainer.threadManager->mutex.unlock(); #endif diff --git a/core/libs/dimg/filters/icc/iccprofile.cpp b/core/libs/dimg/filters/icc/iccprofile.cpp index 7502c9a361..e50060ee04 100644 --- a/core/libs/dimg/filters/icc/iccprofile.cpp +++ b/core/libs/dimg/filters/icc/iccprofile.cpp @@ -1,620 +1,620 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-08-07 * Description : a wrapper class for an ICC color profile * * Copyright (C) 2005-2006 by F.J. Cruz * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "iccprofile.h" #include "digikam-lcms.h" // Qt includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "dimg.h" namespace Digikam { class Q_DECL_HIDDEN IccProfile::Private : public QSharedData { public: explicit Private() : type(IccProfile::InvalidType), handle(nullptr) { } explicit Private(const Private& other) : QSharedData(other) { handle = nullptr; operator = (other); } Private& operator=(const Private& other) { data = other.data; filePath = other.filePath; description = other.description; type = other.type; close(); handle = nullptr; return *this; } ~Private() { close(); } void close() { if (handle) { LcmsLock lock; dkCmsCloseProfile(handle); handle = nullptr; } } public: QByteArray data; QString filePath; QString description; IccProfile::ProfileType type; cmsHPROFILE handle; }; // ---------------------------------------------------------------------------------- class Q_DECL_HIDDEN IccProfileStatic { public: IccProfileStatic() : lcmsMutex() { } QMutex lcmsMutex; QString adobeRGBPath; }; Q_GLOBAL_STATIC(IccProfileStatic, static_d) // ---------------------------------------------------------------------------------- LcmsLock::LcmsLock() { static_d->lcmsMutex.lock(); } LcmsLock::~LcmsLock() { static_d->lcmsMutex.unlock(); } // ---------------------------------------------------------------------------------- IccProfile::IccProfile() : d(nullptr) { } IccProfile::IccProfile(const QByteArray& data) : d(new Private) { d->data = data; } IccProfile::IccProfile(const QString& filePath) : d(new Private) { d->filePath = filePath; } IccProfile::IccProfile(const char* const location, const QString& relativePath) : d(nullptr) { QString filePath; // NOTE: if necessary, implement new location support here. if (QLatin1String(location) == QLatin1String("data")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Searching ICC profile from data directory with relative path:" << relativePath; filePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, relativePath); } else { qCDebug(DIGIKAM_DIMG_LOG) << "Data location " << location << " to handle bundled profile is not supported."; } if (filePath.isNull()) { qCDebug(DIGIKAM_DIMG_LOG) << "The bundled profile" << relativePath << "cannot be found. Check your installation."; return; } d = new Private; d->filePath = filePath; } IccProfile IccProfile::sRGB() { // The srgb.icm file seems to have a whitepoint of D50, see #133913 return IccProfile("data", QLatin1String("digikam/profiles/srgb-d65.icm")); } IccProfile IccProfile::adobeRGB() { QString path = static_d->adobeRGBPath; if (path.isEmpty()) { path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/profiles/compatibleWithAdobeRGB1998.icc")); } return IccProfile(path); } IccProfile IccProfile::wideGamutRGB() { return IccProfile("data", QLatin1String("digikam/profiles/widegamut.icm")); } IccProfile IccProfile::proPhotoRGB() { return IccProfile("data", QLatin1String("digikam/profiles/prophoto.icm")); } QList IccProfile::defaultProfiles() { QList profiles; profiles << sRGB() << adobeRGB() << proPhotoRGB() << wideGamutRGB(); return profiles; } IccProfile::IccProfile(const IccProfile& other) : d(other.d) { } IccProfile::~IccProfile() { } IccProfile& IccProfile::operator=(const IccProfile& other) { d = other.d; return *this; } bool IccProfile::isNull() const { return !d; } bool IccProfile::operator==(const IccProfile& other) const { if (d == other.d) { return true; } if (d && other.d) { if (!d->filePath.isNull() || !other.d->filePath.isNull()) { return (d->filePath == other.d->filePath); } if (!d->data.isNull() || other.d->data.isNull()) { return (d->data == other.d->data); } } return false; } bool IccProfile::isSameProfileAs(IccProfile& other) { if (d == other.d) { return true; } if (d && other.d) { // uses memcmp return (data() == other.data()); } return false; } QByteArray IccProfile::data() { if (!d) { return QByteArray(); } if (!d->data.isEmpty()) { return d->data; } else if (!d->filePath.isNull()) { QFile file(d->filePath); if (!file.open(QIODevice::ReadOnly)) { return QByteArray(); } d->data = file.readAll(); file.close(); return d->data; } return QByteArray(); } bool IccProfile::open() { if (!d) { return false; } if (d->handle) { return true; } if (!d->data.isEmpty()) { LcmsLock lock; d->handle = dkCmsOpenProfileFromMem(d->data.data(), (DWORD)d->data.size()); } else if (!d->filePath.isNull()) { // read file data(); if (d->data.isEmpty()) { return false; } LcmsLock lock; d->handle = dkCmsOpenProfileFromMem(d->data.data(), (DWORD)d->data.size()); } return d->handle; } void IccProfile::close() { if (!d) { return; } d->close(); } bool IccProfile::isOpen() const { if (!d) { return false; } return d->handle; } QString IccProfile::filePath() const { if (!d) { return QString(); } return d->filePath; } QString IccProfile::description() { if (!d) { return QString(); } if (!d->description.isNull()) { return d->description; } if (!open()) { return QString(); } LcmsLock lock; if (!QString(dkCmsTakeProductDesc(d->handle)).isEmpty()) { d->description = QString(dkCmsTakeProductDesc(d->handle)).replace(QLatin1Char('\n'), QLatin1Char(' ')); } return d->description; } IccProfile::ProfileType IccProfile::type() { if (!d) { return InvalidType; } if (d->type != InvalidType) { return d->type; } if (!open()) { return InvalidType; } LcmsLock lock; switch ((int)dkCmsGetDeviceClass(d->handle)) { case icSigInputClass: case 0x6e6b7066: // 'nkbf', proprietary in Nikon profiles d->type = Input; break; case icSigDisplayClass: d->type = Display; break; case icSigOutputClass: d->type = Output; break; case icSigColorSpaceClass: d->type = ColorSpace; break; case icSigLinkClass: d->type = DeviceLink; break; case icSigAbstractClass: d->type = Abstract; break; case icSigNamedColorClass: d->type = NamedColor; break; default: break; } return d->type; } bool IccProfile::writeToFile(const QString& filePath) { if (!d) { return false; } QByteArray profile = data(); if (!profile.isEmpty()) { QFile file(filePath); if (!file.open(QIODevice::WriteOnly)) { return false; } if (file.write(profile) == -1) { return false; } file.close(); return true; } return false; } void* IccProfile::handle() const { if (!d) { return nullptr; } return d->handle; } QStringList IccProfile::defaultSearchPaths() { QStringList paths; QStringList candidates; paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("color/icc"), QStandardPaths::LocateDirectory); #ifdef Q_OS_WIN candidates << QDir::rootPath() + QLatin1String("/Windows/Spool/Drivers/Color/"); // For Win2K and WinXP candidates << QDir::rootPath() + QLatin1String("/Windows/Color/"); // For Win98 and WinMe #elif defined (Q_OS_OSX) // Use a scheme highly identical to the Linux scheme, adapted for MacPorts in /opt/local, ofcial PKG installer, and the OS X standard ColorSync directories candidates << QLatin1String("/System/Library/ColorSync/Profiles"); candidates << QLatin1String("/Library/ColorSync/Profiles"); candidates << QDir::homePath() + QLatin1String("/Library/ColorSync/Profiles"); // MacPorts installs for KDE, so we include the XDG data dirs, including /usr/share/color/icc QStringList dataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(QLatin1Char(':'), QString::SkipEmptyParts); if (!dataDirs.contains(QLatin1String("/opt/local/share"))) { dataDirs << QLatin1String("/opt/local/share"); } if (!dataDirs.contains(QLatin1String("/opt/digikam/share"))) { dataDirs << QLatin1String("/opt/digikam/share"); } - foreach(const QString& dataDir, dataDirs) + foreach (const QString& dataDir, dataDirs) { candidates << dataDir + QLatin1String("/color/icc"); } // XDG_DATA_HOME QString dataHomeDir = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); if (!dataHomeDir.isEmpty()) { candidates << dataHomeDir + QLatin1String("/color/icc"); candidates << dataHomeDir + QLatin1String("/icc"); } // home dir directories candidates << QDir::homePath() + QLatin1String("/.local/share/color/icc/"); candidates << QDir::homePath() + QLatin1String("/.local/share/icc/"); candidates << QDir::homePath() + QLatin1String("/.color/icc/"); #else // LINUX // XDG data dirs, including /usr/share/color/icc QStringList dataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(QLatin1Char(':'), QString::SkipEmptyParts); if (!dataDirs.contains(QLatin1String("/usr/share"))) { dataDirs << QLatin1String("/usr/share"); } if (!dataDirs.contains(QLatin1String("/usr/local/share"))) { dataDirs << QLatin1String("/usr/local/share"); } - foreach(const QString& dataDir, dataDirs) + foreach (const QString& dataDir, dataDirs) { candidates << dataDir + QLatin1String("/color/icc"); } // XDG_DATA_HOME QString dataHomeDir = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); if (!dataHomeDir.isEmpty()) { candidates << dataHomeDir + QLatin1String("/color/icc"); candidates << dataHomeDir + QLatin1String("/icc"); } // home dir directories candidates << QDir::homePath() + QLatin1String("/.local/share/color/icc/"); candidates << QDir::homePath() + QLatin1String("/.local/share/icc/"); candidates << QDir::homePath() + QLatin1String("/.color/icc/"); #endif - foreach(const QString& candidate, candidates) + foreach (const QString& candidate, candidates) { QDir dir(candidate); if (dir.exists() && dir.isReadable()) { QString path = dir.canonicalPath(); if (!paths.contains(path)) { paths << path; } } } //qCDebug(DIGIKAM_DIMG_LOG) << candidates << '\n' << paths; return paths; } void IccProfile::considerOriginalAdobeRGB(const QString& filePath) { if (!static_d->adobeRGBPath.isNull()) { return; } QFile file(filePath); if (file.open(QIODevice::ReadOnly)) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(&file); file.close(); if (md5.result().toHex() == QByteArray("dea88382d899d5f6e573b432473ae138")) { qCDebug(DIGIKAM_DIMG_LOG) << "The original Adobe RGB (1998) profile has been found at" << filePath; static_d->adobeRGBPath = filePath; } } } } // namespace Digikam diff --git a/core/libs/dimg/filters/icc/iccprofilesettings.cpp b/core/libs/dimg/filters/icc/iccprofilesettings.cpp index a336f03958..c36375774e 100644 --- a/core/libs/dimg/filters/icc/iccprofilesettings.cpp +++ b/core/libs/dimg/filters/icc/iccprofilesettings.cpp @@ -1,160 +1,160 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-02-17 * Description : Icc profile settings view. * * Copyright (C) 2010-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "iccprofilesettings.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "dexpanderbox.h" #include "dnuminput.h" #include "digikam_debug.h" #include "iccprofilescombobox.h" #include "iccprofileinfodlg.h" #include "iccsettings.h" namespace Digikam { class Q_DECL_HIDDEN IccProfilesSettings::Private { public: explicit Private() : profilesBox(nullptr) { favoriteProfiles.setMaxCost(10); } static const QString configRecentlyUsedProfilesEntry; QCache favoriteProfiles; IccProfilesComboBox* profilesBox; }; const QString IccProfilesSettings::Private::configRecentlyUsedProfilesEntry(QLatin1String("Recently Used Profiles")); // -------------------------------------------------------- IccProfilesSettings::IccProfilesSettings(QWidget* const parent) : DVBox(parent), d(new Private) { QLabel* const newProfileLabel = new QLabel(i18n("Convert to:"), this); d->profilesBox = new IccProfilesComboBox(this); d->profilesBox->addProfilesSqueezed(IccSettings::instance()->workspaceProfiles()); d->profilesBox->setWhatsThis(i18n("Select the profile of the color space to convert to.")); newProfileLabel->setBuddy(d->profilesBox); QPushButton* const newProfInfo = new QPushButton(i18n("Info..."), this); layout()->setAlignment(newProfInfo, Qt::AlignLeft); setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setContentsMargins(QMargins()); // ------------------------------------------------------------- connect(d->profilesBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotProfileChanged())); connect(newProfInfo, SIGNAL(clicked()), this, SLOT(slotNewProfInfo())); } IccProfilesSettings::~IccProfilesSettings() { delete d; } void IccProfilesSettings::slotNewProfInfo() { ICCProfileInfoDlg infoDlg(qApp->activeWindow(), QString(), d->profilesBox->currentProfile()); infoDlg.exec(); } void IccProfilesSettings::slotProfileChanged() { d->favoriteProfiles.insert(d->profilesBox->currentProfile().filePath(), new bool(true)); emit signalSettingsChanged(); } IccProfile IccProfilesSettings::currentProfile() const { return d->profilesBox->currentProfile(); } void IccProfilesSettings::setCurrentProfile(const IccProfile& prof) { blockSignals(true); d->profilesBox->setCurrentProfile(prof); blockSignals(false); } void IccProfilesSettings::resetToDefault() { blockSignals(true); d->profilesBox->setCurrentIndex(0); blockSignals(false); } IccProfile IccProfilesSettings::defaultProfile() const { return d->profilesBox->itemData(0).value(); } void IccProfilesSettings::readSettings(KConfigGroup& group) { QStringList lastProfiles = group.readPathEntry(d->configRecentlyUsedProfilesEntry, QStringList()); - foreach(const QString& path, lastProfiles) + foreach (const QString& path, lastProfiles) { d->favoriteProfiles.insert(path, new bool(true)); } } void IccProfilesSettings::writeSettings(KConfigGroup& group) { group.writePathEntry(d->configRecentlyUsedProfilesEntry, d->favoriteProfiles.keys()); } // Static Methods. QStringList IccProfilesSettings::favoriteProfiles(KConfigGroup& group) { Private d; return group.readPathEntry(d.configRecentlyUsedProfilesEntry, QStringList()); } } // namespace Digikam diff --git a/core/libs/dimg/filters/icc/iccsettings.cpp b/core/libs/dimg/filters/icc/iccsettings.cpp index 3d24bd738c..34bc690ff6 100644 --- a/core/libs/dimg/filters/icc/iccsettings.cpp +++ b/core/libs/dimg/filters/icc/iccsettings.cpp @@ -1,641 +1,641 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-08-09 * Description : central place for ICC settings * * Copyright (C) 2005-2006 by F.J. Cruz * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "iccsettings.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "iccprofile.h" #include "icctransform.h" // X11 includes // Note must be after all other to prevent broken compilation #ifdef HAVE_X11 # include # include # include # include #endif // HAVE_X11 namespace Digikam { class Q_DECL_HIDDEN IccSettings::Private { public: explicit Private() : settings(ICCSettingsContainer()), configGroup(QLatin1String("Color Management")) { } QList scanDirectories(const QStringList& dirs); void scanDirectory(const QString& path, const QStringList& filter, QList* const profiles); IccProfile profileFromWindowSystem(QWidget* const widget); ICCSettingsContainer readFromConfig() const; void writeToConfig() const; void writeManagedViewToConfig() const; void writeManagedPreviewsToConfig() const; public: ICCSettingsContainer settings; QMutex mutex; QList profiles; QHash screenProfiles; const QString configGroup; }; // ----------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN IccSettingsCreator { public: IccSettings object; }; Q_GLOBAL_STATIC(IccSettingsCreator, creator) // ----------------------------------------------------------------------------------------------- IccSettings* IccSettings::instance() { return &creator->object; } IccSettings::IccSettings() : d(new Private) { IccTransform::init(); readFromConfig(); qRegisterMetaType("ICCSettingsContainer"); } IccSettings::~IccSettings() { delete d; } ICCSettingsContainer IccSettings::settings() { QMutexLocker lock(&d->mutex); ICCSettingsContainer s(d->settings); return s; } IccProfile IccSettings::monitorProfile(QWidget* const widget) { // system-wide profile set? IccProfile profile = d->profileFromWindowSystem(widget); if (!profile.isNull()) { return profile; } QMutexLocker lock(&d->mutex); if (!d->settings.monitorProfile.isNull()) { return IccProfile(d->settings.monitorProfile); } else { return IccProfile::sRGB(); } } bool IccSettings::monitorProfileFromSystem() const { // First, look into cache { QMutexLocker lock(&d->mutex); - foreach(const IccProfile& profile, d->screenProfiles) + foreach (const IccProfile& profile, d->screenProfiles) { if (!profile.isNull()) { return true; } } } // Second, check all toplevel widgets QList topLevels = qApp->topLevelWidgets(); foreach (QWidget* const widget, topLevels) { if (!d->profileFromWindowSystem(widget).isNull()) { return true; } } return false; } /* * From koffice/libs/pigment/colorprofiles/KoLcmsColorProfileContainer.cpp * Copyright (C) 2000 Matthias Elter * 2001 John Califf * 2004 Boudewijn Rempt * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Adrian Page */ IccProfile IccSettings::Private::profileFromWindowSystem(QWidget* const widget) { #ifdef HAVE_X11 if (!QX11Info::isPlatformX11()) { qCDebug(DIGIKAM_DIMG_LOG) << "Desktop platform is not X11"; return IccProfile(); } unsigned long appRootWindow; QString atomName; QScreen* const screen = qApp->primaryScreen(); if (!screen) { qCDebug(DIGIKAM_DIMG_LOG) << "No screen available for application"; return IccProfile(); } QScreen* widgetScreen = screen; if (widget) { QWindow* winHandle = widget->windowHandle(); if (!winHandle) { if (QWidget* const nativeParent = widget->nativeParentWidget()) { winHandle = nativeParent->windowHandle(); } } if (winHandle) { widgetScreen = winHandle->screen(); } } int screenNumber = qMax(qApp->screens().indexOf(widgetScreen), 0); IccProfile profile; { QMutexLocker lock(&mutex); if (screenProfiles.contains(screenNumber)) { return screenProfiles.value(screenNumber); } } if (screen->virtualSiblings().size() > 1) { appRootWindow = QX11Info::appRootWindow(QX11Info::appScreen()); atomName = QString::fromLatin1("_ICC_PROFILE_%1").arg(screenNumber); } else { appRootWindow = QX11Info::appRootWindow(screenNumber); atomName = QLatin1String("_ICC_PROFILE"); } Atom type; int format; unsigned long nitems; unsigned long bytes_after; quint8* str = nullptr; static Atom icc_atom = XInternAtom(QX11Info::display(), atomName.toLatin1().constData(), True); if ((icc_atom != None) && (XGetWindowProperty(QX11Info::display(), appRootWindow, icc_atom, 0, INT_MAX, False, XA_CARDINAL, &type, &format, &nitems, &bytes_after, (unsigned char**)& str) == Success) && nitems ) { QByteArray bytes = QByteArray::fromRawData((char*)str, (quint32)nitems); if (!bytes.isEmpty()) { profile = IccProfile(bytes); } qCDebug(DIGIKAM_DIMG_LOG) << "Found X.org XICC monitor profile " << profile.description(); } else { qCDebug(DIGIKAM_DIMG_LOG) << "No X.org XICC profile installed for screen " << screenNumber; } // insert to cache even if null { QMutexLocker lock(&mutex); screenProfiles.insert(screenNumber, profile); } #elif defined Q_OS_WIN //TODO Q_UNUSED(widget); #elif defined Q_OS_OSX //TODO Q_UNUSED(widget); #else // Unsupported platform Q_UNUSED(widget); #endif return IccProfile(); } bool IccSettings::isEnabled() const { return d->settings.enableCM; } bool IccSettings::useManagedPreviews() const { return (isEnabled() && d->settings.useManagedPreviews); } ICCSettingsContainer IccSettings::Private::readFromConfig() const { ICCSettingsContainer s; KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); s.readFromConfig(group); return s; } void IccSettings::Private::writeToConfig() const { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); settings.writeToConfig(group); } void IccSettings::Private::writeManagedViewToConfig() const { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); settings.writeManagedViewToConfig(group); } void IccSettings::Private::writeManagedPreviewsToConfig() const { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroup); settings.writeManagedPreviewsToConfig(group); } void IccSettings::readFromConfig() { ICCSettingsContainer old, s; s = d->readFromConfig(); { QMutexLocker lock(&d->mutex); old = d->settings; d->settings = s; } emit settingsChanged(); emit settingsChanged(s, old); } void IccSettings::setSettings(const ICCSettingsContainer& settings) { ICCSettingsContainer old; { QMutexLocker lock(&d->mutex); if (settings.iccFolder != d->settings.iccFolder) { d->profiles.clear(); } old = d->settings; d->settings = settings; } d->writeToConfig(); emit settingsChanged(); emit settingsChanged(settings, old); } void IccSettings::setUseManagedView(bool useManagedView) { ICCSettingsContainer old, current; { QMutexLocker lock(&d->mutex); old = d->settings; d->settings.useManagedView = useManagedView; current = d->settings; } d->writeManagedViewToConfig(); emit settingsChanged(); emit settingsChanged(current, old); } void IccSettings::setUseManagedPreviews(bool useManagedPreviews) { ICCSettingsContainer old, current; { QMutexLocker lock(&d->mutex); old = d->settings; d->settings.useManagedPreviews = useManagedPreviews; current = d->settings; } d->writeManagedPreviewsToConfig(); emit settingsChanged(); emit settingsChanged(current, old); } void IccSettings::setIccPath(const QString& path) { ICCSettingsContainer old, current; { QMutexLocker lock(&d->mutex); if (path == d->settings.iccFolder) { return; } d->profiles.clear(); old = d->settings; d->settings.iccFolder = path; current = d->settings; } d->writeManagedViewToConfig(); emit settingsChanged(); emit settingsChanged(current, old); } QList IccSettings::Private::scanDirectories(const QStringList& dirs) { QList profiles; QStringList filters; filters << QLatin1String("*.icc") << QLatin1String("*.icm"); qCDebug(DIGIKAM_DIMG_LOG) << dirs; foreach (const QString& dirPath, dirs) { QDir dir(dirPath); if (!dir.exists()) { continue; } scanDirectory(dir.path(), filters, &profiles); } return profiles; } void IccSettings::Private::scanDirectory(const QString& path, const QStringList& filter, QList* const profiles) { QDir dir(path); QFileInfoList infos; infos << dir.entryInfoList(filter, QDir::Files | QDir::Readable); infos << dir.entryInfoList(QDir::Dirs | QDir::Readable | QDir::NoDotAndDotDot); foreach (const QFileInfo& info, infos) { if (info.isFile()) { //qCDebug(DIGIKAM_DIMG_LOG) << info.filePath() << (info.exists() && info.isReadable()); IccProfile profile(info.filePath()); if (profile.open()) { *profiles << profile; if (info.fileName() == QLatin1String("AdobeRGB1998.icc")) { IccProfile::considerOriginalAdobeRGB(info.filePath()); } } } else if (info.isDir() && !info.isSymLink()) { scanDirectory(info.filePath(), filter, profiles); } } } QList IccSettings::allProfiles() { QString extraPath; { QMutexLocker lock(&d->mutex); if (!d->profiles.isEmpty()) { return d->profiles; } extraPath = d->settings.iccFolder; } QList profiles; // get system paths, e.g. /usr/share/color/icc QStringList paths = IccProfile::defaultSearchPaths(); // add user-specified path if (!extraPath.isEmpty() && !paths.contains(extraPath)) { paths << extraPath; } // check search directories profiles << d->scanDirectories(paths); // load profiles that come with RawEngine profiles << IccProfile::defaultProfiles(); QMutexLocker lock(&d->mutex); d->profiles = profiles; return d->profiles; } QList IccSettings::workspaceProfiles() { QList profiles; foreach (IccProfile profile, allProfiles()) // krazy:exclude=foreach { switch (profile.type()) { case IccProfile::Display: case IccProfile::ColorSpace: profiles << profile; break; default: break; } } return profiles; } QList IccSettings::displayProfiles() { QList profiles; - foreach(IccProfile profile, allProfiles()) // krazy:exclude=foreach + foreach (IccProfile profile, allProfiles()) // krazy:exclude=foreach { if (profile.type() == IccProfile::Display) { profiles << profile; } } return profiles; } QList IccSettings::inputProfiles() { QList profiles; foreach (IccProfile profile, allProfiles()) // krazy:exclude=foreach { switch (profile.type()) { case IccProfile::Input: case IccProfile::ColorSpace: profiles << profile; break; default: break; } } return profiles; } QList IccSettings::outputProfiles() { QList profiles; - foreach(IccProfile profile, allProfiles()) // krazy:exclude=foreach + foreach (IccProfile profile, allProfiles()) // krazy:exclude=foreach { if (profile.type() == IccProfile::Output) { profiles << profile; } } return profiles; } QList IccSettings::profilesForDescription(const QString& description) { QList profiles; if (description.isEmpty()) { return profiles; } foreach (IccProfile profile, allProfiles()) // krazy:exclude=foreach { if (profile.description() == description) { profiles << profile; } } return profiles; } void IccSettings::loadAllProfilesProperties() { allProfiles(); const int size = d->profiles.size(); for (int i = 0 ; i < size ; ++i) { IccProfile& profile = d->profiles[i]; if (!profile.isOpen()) { profile.description(); profile.type(); profile.close(); } else { profile.description(); profile.type(); } } } } // namespace Digikam diff --git a/core/libs/dimg/filters/lc/localcontrastfilter.cpp b/core/libs/dimg/filters/lc/localcontrastfilter.cpp index e32979ebf3..fa1f230cd2 100644 --- a/core/libs/dimg/filters/lc/localcontrastfilter.cpp +++ b/core/libs/dimg/filters/lc/localcontrastfilter.cpp @@ -1,741 +1,741 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-08-09 * Description : Enhance image with local contrasts (as human eye does). * LDR ToneMapper * * Copyright (C) 2009 by Nasca Octavian Paul * Copyright (C) 2009 by Julien Pontabry * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "localcontrastfilter.h" // Qt includes #include #include // krazy:exclude=includes // KDE includes #include // Local includes #include "digikam_debug.h" #include "randomnumbergenerator.h" namespace Digikam { class Q_DECL_HIDDEN LocalContrastFilter::Private { public: explicit Private() { current_process_power_value = 20.0; } // preprocessed values float current_process_power_value; LocalContrastContainer par; RandomNumberGenerator generator; }; LocalContrastFilter::LocalContrastFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } LocalContrastFilter::LocalContrastFilter(DImg* const image, QObject* const parent, const LocalContrastContainer& par) : DImgThreadedFilter(image, parent, QLatin1String("LocalContrast")), d(new Private) { d->par = par; d->generator.seedByTime(); initFilter(); } LocalContrastFilter::~LocalContrastFilter() { cancelFilter(); delete d; } QString LocalContrastFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Local Contrast Filter")); } void LocalContrastFilter::filterImage() { if (!m_orgImage.isNull()) { int size = m_orgImage.width() * m_orgImage.height() * 3; int i, j; d->generator.reseed(); if (m_orgImage.sixteenBit()) { // sixteen bit image QScopedArrayPointer data(new unsigned short[size]); unsigned short* dataImg = reinterpret_cast(m_orgImage.bits()); for (i = 0, j = 0; runningFlag() && (i < size); i += 3, j += 4) { data[i] = dataImg[j]; data[i + 1] = dataImg[j + 1]; data[i + 2] = dataImg[j + 2]; } postProgress(10); process16bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height()); for (uint x = 0; runningFlag() && (x < m_orgImage.width()); ++x) { for (uint y = 0; runningFlag() && (y < m_orgImage.height()); ++y) { i = (m_orgImage.width() * y + x) * 3; m_destImage.setPixelColor(x, y, DColor((unsigned short)data[i + 2], (unsigned short)data[i + 1], (unsigned short)data[i], 65535, true)); } } postProgress(90); } else { // eight bit image QScopedArrayPointer data(new uchar[size]); for (i = 0, j = 0; runningFlag() && (i < size); i += 3, j += 4) { data[i] = m_orgImage.bits()[j]; data[i + 1] = m_orgImage.bits()[j + 1]; data[i + 2] = m_orgImage.bits()[j + 2]; } postProgress(10); process8bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height()); for (uint x = 0; runningFlag() && (x < m_orgImage.width()); ++x) { for (uint y = 0; runningFlag() && (y < m_orgImage.height()); ++y) { i = (m_orgImage.width() * y + x) * 3; m_destImage.setPixelColor(x, y, DColor(data[i + 2], data[i + 1], data[i], 255, false)); } } postProgress(90); } } postProgress(100); } void LocalContrastFilter::process8bitRgbImage(unsigned char* const img, int sizex, int sizey) { int size = sizex * sizey; QScopedArrayPointer tmpimage(new float[size * 3]); for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i) { // convert to floating point tmpimage[i] = (float)(img[i] / 255.0); } postProgress(20); processRgbImage(tmpimage.data(), sizex, sizey); // convert back to 8 bits (with dithering) int pos = 0; for (int i = 0 ; runningFlag() && (i < size) ; ++i) { float dither = d->generator.number(0.0, 1.0); img[pos] = (int)(tmpimage[pos] * 255.0 + dither); img[pos + 1] = (int)(tmpimage[pos + 1] * 255.0 + dither); img[pos + 2] = (int)(tmpimage[pos + 2] * 255.0 + dither); pos += 3; } postProgress(80); } void LocalContrastFilter::process16bitRgbImage(unsigned short* const img, int sizex, int sizey) { int size = sizex * sizey; QScopedArrayPointer tmpimage(new float[size * 3]); for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i) { // convert to floating point tmpimage[i] = (float)(img[i] / 65535.0); } postProgress(20); processRgbImage(tmpimage.data(), sizex, sizey); // convert back to 16 bits (with dithering) int pos = 0; for (int i = 0 ; runningFlag() && (i < size) ; ++i) { float dither = d->generator.number(0.0, 1.0); img[pos] = (int)(tmpimage[pos] * 65535.0 + dither); img[pos + 1] = (int)(tmpimage[pos + 1] * 65535.0 + dither); img[pos + 2] = (int)(tmpimage[pos + 2] * 65535.0 + dither); pos += 3; } postProgress(80); } float LocalContrastFilter::func(float x1, float x2) { float result = 0.5; float p; switch (d->par.functionId) { case 0: // power function { p = (float)(qPow((double)10.0, (double)qFabs((x2 * 2.0 - 1.0)) * d->current_process_power_value * 0.02)); if (x2 >= 0.5) { result = qPow(x1, p); } else { result = (float)(1.0 - qPow((double)1.0 - x1, (double)p)); } break; } case 1: // linear function { p = (float)(1.0 / (1 + qExp(-(x2 * 2.0 - 1.0) * d->current_process_power_value * 0.04))); result = (x1 < p) ? (float)(x1 * (1.0 - p) / p) : (float)((1.0 - p) + (x1 - p) * p / (1.0 - p)); break; } } return result; } void LocalContrastFilter::blurMultithreaded(uint start, uint stop, float* const img, float* const blurimage) { uint pos = start * 3; for (uint i = start ; runningFlag() && (i < stop) ; ++i) { float src_r = img[pos]; float src_g = img[pos + 1]; float src_b = img[pos + 2]; float blur = blurimage[i]; float dest_r = func(src_r, blur); float dest_g = func(src_g, blur); float dest_b = func(src_b, blur); img[pos] = dest_r; img[pos + 1] = dest_g; img[pos + 2] = dest_b; pos += 3; } } void LocalContrastFilter::saturationMultithreaded(uint start, uint stop, float* const img, float* const srcimg) { float src_h, src_s, src_v; float dest_h, dest_s, dest_v; float destSaturation, s1; uint pos = start * 3; int highSaturationValue = 100 - d->par.highSaturation; int lowSaturationValue = 100 - d->par.lowSaturation; for (uint i = start ; runningFlag() && (i < stop) ; ++i) { rgb2hsv(srcimg[pos], srcimg[pos + 1], srcimg[pos + 2], src_h, src_s, src_v); rgb2hsv(img[pos], img[pos + 1], img[pos + 2], dest_h, dest_s, dest_v); destSaturation = (float)((src_s * highSaturationValue + dest_s * (100.0 - highSaturationValue)) * 0.01); if (dest_v > src_v) { s1 = (float)(destSaturation * src_v / (dest_v + 1.0 / 255.0)); destSaturation = (float)((lowSaturationValue * s1 + d->par.lowSaturation * destSaturation) * 0.01); } hsv2rgb(dest_h, destSaturation, dest_v, img[pos], img[pos + 1], img[pos + 2]); pos += 3; } } void LocalContrastFilter::processRgbImage(float* const img, int sizex, int sizey) { int size = sizex * sizey; QScopedArrayPointer blurimage(new float[size]); QScopedArrayPointer srcimg(new float[size * 3]); for (int i = 0 ; i < (size * 3) ; ++i) { srcimg[i] = img[i]; } postProgress(30); if (d->par.stretchContrast) { stretchContrast(img, size * 3); } postProgress(40); QList vals = multithreadedSteps(size); int pos = 0; for (int nstage = 0 ; runningFlag() && (nstage < TONEMAPPING_MAX_STAGES) ; ++nstage) { if (d->par.stage[nstage].enabled) { // compute the desatured image pos = 0; for (int i = 0 ; runningFlag() && (i < size) ; ++i) { blurimage[i] = (float)((img[pos] + img[pos + 1] + img[pos + 2]) / 3.0); pos += 3; } d->current_process_power_value = d->par.getPower(nstage); // blur inplaceBlur(blurimage.data(), sizex, sizey, d->par.getBlur(nstage)); QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &LocalContrastFilter::blurMultithreaded, vals[j], vals[j+1], img, blurimage.data() )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } postProgress(50 + nstage * 5); } if ((d->par.highSaturation != 100) || (d->par.lowSaturation != 100)) { qCDebug(DIGIKAM_DIMG_LOG) << "highSaturation : " << d->par.highSaturation; qCDebug(DIGIKAM_DIMG_LOG) << "lowSaturation : " << d->par.lowSaturation; QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &LocalContrastFilter::saturationMultithreaded, vals[j], vals[j+1], img, srcimg.data() )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } postProgress(70); } void LocalContrastFilter::inplaceBlurYMultithreaded(const Args& prm) { for (uint y = prm.start ; runningFlag() && (y < prm.stop) ; ++y) { uint pos = y * prm.sizex; float old = prm.data[pos]; ++pos; for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x) { old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; prm.data[pos] = old; ++pos; } pos = y * prm.sizex + prm.sizex - 1; for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x) { old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; prm.data[pos] = old; pos--; } } } void LocalContrastFilter::inplaceBlurXMultithreaded(const Args& prm) { for (uint x = prm.start ; runningFlag() && (x < prm.stop) ; ++x) { uint pos = x; float old = prm.data[pos]; for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y) { old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; prm.data[pos] = old; pos += prm.sizex; } pos = x + prm.sizex * (prm.sizey - 1); for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y) { old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove; prm.data[pos] = old; pos -= prm.sizex; } } } void LocalContrastFilter::inplaceBlur(float* const data, int sizex, int sizey, float blur) { if (blur < 0.3) { return; } Args prm; prm.a = (float)(qExp(log(0.25) / blur)); if ((prm.a <= 0.0) || (prm.a >= 1.0)) { return; } prm.a *= prm.a; prm.data = data; prm.sizex = sizex; prm.sizey = sizey; prm.blur = blur; prm.denormal_remove = (float)(1e-15); QList valsx = multithreadedSteps(prm.sizex); QList valsy = multithreadedSteps(prm.sizey); for (uint stage = 0 ; runningFlag() && (stage < 2) ; ++stage) { QList > tasks; for (int j = 0 ; runningFlag() && (j < valsy.count()-1) ; ++j) { prm.start = valsy[j]; prm.stop = valsy[j+1]; tasks.append(QtConcurrent::run(this, &LocalContrastFilter::inplaceBlurYMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); tasks.clear(); for (int j = 0 ; runningFlag() && (j < valsx.count()-1) ; ++j) { prm.start = valsx[j]; prm.stop = valsx[j+1]; tasks.append(QtConcurrent::run(this, &LocalContrastFilter::inplaceBlurXMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); } } void LocalContrastFilter::stretchContrast(float* const data, int datasize) { // stretch the contrast const unsigned int histogram_size = 256; // first, we compute the histogram unsigned int histogram[histogram_size]; for (unsigned int i = 0 ; i < histogram_size ; ++i) { histogram[i] = 0; } for (unsigned int i = 0 ; runningFlag() && (i < (unsigned int)datasize) ; ++i) { int m = (int)(data[i] * (histogram_size - 1)); if (m < 0) { m = 0; } if (m > (int)(histogram_size - 1)) { m = histogram_size - 1; } histogram[m]++; } // I want to strip the lowest and upper 0.1 percents (in the histogram) of the pixels int min = 0; int max = 255; unsigned int desired_sum = datasize / 1000; unsigned int sum_min = 0; unsigned int sum_max = 0; for (unsigned int i = 0 ; runningFlag() && (i < histogram_size) ; ++i) { sum_min += histogram[i]; if (sum_min > desired_sum) { min = i; break; } } for (int i = histogram_size - 1 ; runningFlag() && (i >= 0) ; --i) { sum_max += histogram[i]; if (sum_max > desired_sum) { max = i; break; } } if (min >= max) { min = 0; max = 255; } float min_src_val = (float)(min / 255.0); float max_src_val = (float)(max / 255.0); for (int i = 0 ; runningFlag() && (i < datasize) ; ++i) { // stretch the contrast float x = data[i]; x = (x - min_src_val) / (max_src_val - min_src_val); if (x < 0.0) { x = 0.0; } if (x > 1.0) { x = 1.0; } data[i] = x; } } void LocalContrastFilter::rgb2hsv(const float& r, const float& g, const float& b, float& h, float& s, float& v) { float maxrg = (r > g) ? r : g; float max = (maxrg > b) ? maxrg : b; float minrg = (r < g) ? r : g; float min = (minrg < b) ? minrg : b; float delta = max - min; //hue if (min == max) { h = 0.0; } else { if (max == r) { h = (float)(fmod(60.0 * (g - b) / delta + 360.0, 360.0)); } else { if (max == g) { h = (float)(60.0 * (b - r) / delta + 120.0); } else { //max==b h = (float)(60.0 * (r - g) / delta + 240.0); } } } //saturation if (max < 1e-6) { s = 0; } else { s = (float)(1.0 - min / max); } //value v = max; } void LocalContrastFilter::hsv2rgb(const float& h, const float& s, const float& v, float& r, float& g, float& b) { float hfi = (float)(floor(h / 60.0)); float f = (float)((h / 60.0) - hfi); int hi = ((int)hfi) % 6; float p = (float)(v * (1.0 - s)); float q = (float)(v * (1.0 - f * s)); float t = (float)(v * (1.0 - (1.0 - f) * s)); switch (hi) { case 0: r = v ; g = t ; b = p; break; case 1: r = q ; g = v ; b = p; break; case 2: r = p ; g = v ; b = t; break; case 3: r = p ; g = q ; b = v; break; case 4: r = t ; g = p; b = v; break; case 5: r = v ; g = p ; b = q; break; } } FilterAction LocalContrastFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("functionId"), d->par.functionId); action.addParameter(QLatin1String("highSaturation"), d->par.highSaturation); action.addParameter(QLatin1String("lowSaturation"), d->par.lowSaturation); action.addParameter(QLatin1String("stretchContrast"), d->par.stretchContrast); for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES ; ++nstage) { QString stage = QString::fromLatin1("stage[%1]:").arg(nstage); action.addParameter(stage + QLatin1String("enabled"), d->par.stage[nstage].enabled); if (d->par.stage[nstage].enabled) { action.addParameter(stage + QLatin1String("power"), d->par.stage[nstage].power); action.addParameter(stage + QLatin1String("blur"), d->par.stage[nstage].blur); } } action.addParameter(QLatin1String("randomSeed"), d->generator.currentSeed()); return action; } void LocalContrastFilter::readParameters(const FilterAction& action) { d->par.functionId = action.parameter(QLatin1String("functionId")).toInt(); d->par.highSaturation = action.parameter(QLatin1String("highSaturation")).toInt(); d->par.lowSaturation = action.parameter(QLatin1String("lowSaturation")).toInt(); d->par.stretchContrast = action.parameter(QLatin1String("stretchContrast")).toBool(); for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES ; ++nstage) { QString stage = QString::fromLatin1("stage[%1]:").arg(nstage); d->par.stage[nstage].enabled = action.parameter(stage + QLatin1String("enabled")).toBool(); if (d->par.stage[nstage].enabled) { d->par.stage[nstage].power = action.parameter(stage + QLatin1String("power")).toFloat(); d->par.stage[nstage].blur = action.parameter(stage + QLatin1String("blur")).toFloat(); } } d->generator.seed(action.parameter(QLatin1String("randomSeed")).toUInt()); } } // namespace Digikam diff --git a/core/libs/dimg/filters/lens/lensfunfilter.cpp b/core/libs/dimg/filters/lens/lensfunfilter.cpp index 5965a070a4..cde04ae605 100644 --- a/core/libs/dimg/filters/lens/lensfunfilter.cpp +++ b/core/libs/dimg/filters/lens/lensfunfilter.cpp @@ -1,426 +1,426 @@ /* ============================================================ * * Date : 2008-02-10 * Description : a tool to fix automatically camera lens aberrations * * Copyright (C) 2008 by Adrian Schroeter * Copyright (C) 2008-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "lensfunfilter.h" // Qt includes #include #include #include #include // krazy:exclude=includes // KDE includes #include // Local includes #include "digikam_debug.h" #include "lensfuniface.h" #include "dmetadata.h" // Disable deprecated API from Lensfun. #if defined(Q_CC_GNU) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif namespace Digikam { class Q_DECL_HIDDEN LensFunFilter::Private { public: explicit Private() { iface = nullptr; modifier = nullptr; loop = 0; } DImg tempImage; LensFunIface* iface; lfModifier* modifier; int loop; }; LensFunFilter::LensFunFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { d->iface = new LensFunIface; initFilter(); } LensFunFilter::LensFunFilter(DImg* const orgImage, QObject* const parent, const LensFunContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("LensCorrection")), d(new Private) { d->iface = new LensFunIface; d->iface->setSettings(settings); initFilter(); } LensFunFilter::~LensFunFilter() { cancelFilter(); if (d->modifier) { d->modifier->Destroy(); } delete d->iface; delete d; } QString LensFunFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Lens Auto-Correction Tool")); } void LensFunFilter::filterCCAMultithreaded(uint start, uint stop) { QScopedArrayPointer pos(new float[m_orgImage.width() * 2 * 3]); for (unsigned int y = start; runningFlag() && (y < stop); ++y) { if (d->modifier->ApplySubpixelDistortion(0.0, y, m_orgImage.width(), 1, pos.data())) { float* src = pos.data(); for (unsigned x = 0; runningFlag() && (x < m_destImage.width()); ++x) { DColor destPixel(0, 0, 0, 0xFFFF, m_destImage.sixteenBit()); destPixel.setRed(m_orgImage.getSubPixelColorFast(src[0], src[1]).red()); destPixel.setGreen(m_orgImage.getSubPixelColorFast(src[2], src[3]).green()); destPixel.setBlue(m_orgImage.getSubPixelColorFast(src[4], src[5]).blue()); m_destImage.setPixelColor(x, y, destPixel); src += 2 * 3; } } } } void LensFunFilter::filterVIGMultithreaded(uint start, uint stop) { uchar* data = m_destImage.bits(); data += m_destImage.width() * m_destImage.bytesDepth() * start; for (unsigned int y = start; runningFlag() && (y < stop); ++y) { if (d->modifier->ApplyColorModification(data, 0.0, y, m_destImage.width(), 1, LF_CR_4(RED, GREEN, BLUE, UNKNOWN), 0)) { data += m_destImage.width() * m_destImage.bytesDepth(); } } } void LensFunFilter::filterDSTMultithreaded(uint start, uint stop) { QScopedArrayPointer pos(new float[m_orgImage.width() * 2 * 3]); for (unsigned int y = start; runningFlag() && (y < stop); ++y) { if (d->modifier->ApplyGeometryDistortion(0.0, y, d->tempImage.width(), 1, pos.data())) { float* src = pos.data(); for (unsigned int x = 0; runningFlag() && (x < d->tempImage.width()); ++x, ++d->loop) { d->tempImage.setPixelColor(x, y, m_destImage.getSubPixelColor(src[0], src[1])); src += 2; } } } } void LensFunFilter::filterImage() { m_destImage.bitBltImage(&m_orgImage, 0, 0); if (!d->iface) { qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface is null."; return; } if (!d->iface->usedLens()) { qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface Lens device is null."; return; } // Lensfun Modifier flags to process int modifyFlags = 0; if (d->iface->settings().filterDST) { modifyFlags |= LF_MODIFY_DISTORTION; } if (d->iface->settings().filterGEO) { modifyFlags |= LF_MODIFY_GEOMETRY; } if (d->iface->settings().filterCCA) { modifyFlags |= LF_MODIFY_TCA; } if (d->iface->settings().filterVIG) { modifyFlags |= LF_MODIFY_VIGNETTING; } // Init lensfun lib, we are working on the full image. lfPixelFormat colorDepth = m_orgImage.bytesDepth() == 4 ? LF_PF_U8 : LF_PF_U16; d->modifier = lfModifier::Create(d->iface->usedLens(), d->iface->settings().cropFactor, m_orgImage.width(), m_orgImage.height()); int modflags = d->modifier->Initialize(d->iface->usedLens(), colorDepth, d->iface->settings().focalLength, d->iface->settings().aperture, d->iface->settings().subjectDistance, 1.0, /* no scaling */ LF_RECTILINEAR, modifyFlags, 0 /*no inverse*/); if (!d->modifier) { qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: cannot initialize LensFun Modifier."; return; } // Calc necessary steps for progress bar int steps = ((d->iface->settings().filterCCA) ? 1 : 0) + ((d->iface->settings().filterVIG) ? 1 : 0) + ((d->iface->settings().filterDST || d->iface->settings().filterGEO) ? 1 : 0); qCDebug(DIGIKAM_DIMG_LOG) << "LensFun Modifier Flags: " << modflags << " Steps:" << steps; if (steps < 1) { qCDebug(DIGIKAM_DIMG_LOG) << "No LensFun Modifier steps. There is nothing to process..."; return; } qCDebug(DIGIKAM_DIMG_LOG) << "Image size to process: (" << m_orgImage.width() << ", " << m_orgImage.height() << ")"; QList vals = multithreadedSteps(m_destImage.height()); // Stage 1: Chromatic Aberration Corrections if (d->iface->settings().filterCCA) { m_orgImage.prepareSubPixelAccess(); // init lanczos kernel QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &LensFunFilter::filterCCAMultithreaded, vals[j], vals[j+1])); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); qCDebug(DIGIKAM_DIMG_LOG) << "Chromatic Aberration Corrections applied."; } postProgress(30); // Stage 2: Color Corrections: Vignetting and Color Contribution Index if (d->iface->settings().filterVIG) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &LensFunFilter::filterVIGMultithreaded, vals[j], vals[j+1])); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); qCDebug(DIGIKAM_DIMG_LOG) << "Vignetting and Color Corrections applied."; } postProgress(60); // Stage 3: Distortion and Geometry Corrections if (d->iface->settings().filterDST || d->iface->settings().filterGEO) { d->loop = 0; // we need a deep copy first d->tempImage = DImg(m_destImage.width(), m_destImage.height(), m_destImage.sixteenBit(), m_destImage.hasAlpha()); m_destImage.prepareSubPixelAccess(); // init lanczos kernel QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &LensFunFilter::filterDSTMultithreaded, vals[j], vals[j+1])); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); qCDebug(DIGIKAM_DIMG_LOG) << "Distortion and Geometry Corrections applied."; if (d->loop) { m_destImage = d->tempImage; } } postProgress(90); } bool LensFunFilter::registerSettingsToXmp(MetaEngineData& data) const { // Register in digiKam Xmp namespace all information about Lens corrections. QString str; LensFunContainer prm = d->iface->settings(); str.append(i18n("Camera: %1-%2", prm.cameraMake, prm.cameraModel)); str.append(QLatin1Char('\n')); str.append(i18n("Lens: %1", prm.lensModel)); str.append(QLatin1Char('\n')); str.append(i18n("Subject Distance: %1", prm.subjectDistance)); str.append(QLatin1Char('\n')); str.append(i18n("Aperture: %1", prm.aperture)); str.append(QLatin1Char('\n')); str.append(i18n("Focal Length: %1", prm.focalLength)); str.append(QLatin1Char('\n')); str.append(i18n("Crop Factor: %1", prm.cropFactor)); str.append(QLatin1Char('\n')); str.append(i18n("CCA Correction: %1", prm.filterCCA && d->iface->supportsCCA() ? i18n("enabled") : i18n("disabled"))); str.append(QLatin1Char('\n')); str.append(i18n("VIG Correction: %1", prm.filterVIG && d->iface->supportsVig() ? i18n("enabled") : i18n("disabled"))); str.append(QLatin1Char('\n')); str.append(i18n("DST Correction: %1", prm.filterDST && d->iface->supportsDistortion() ? i18n("enabled") : i18n("disabled"))); str.append(QLatin1Char('\n')); str.append(i18n("GEO Correction: %1", prm.filterGEO && d->iface->supportsGeometry() ? i18n("enabled") : i18n("disabled"))); DMetadata meta(data); bool ret = meta.setXmpTagString("Xmp.digiKam.LensCorrectionSettings", str.replace(QLatin1Char('\n'), QLatin1String(" ; "))); data = meta.data(); return ret; } FilterAction LensFunFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); LensFunContainer prm = d->iface->settings(); action.addParameter(QLatin1String("ccaCorrection"), prm.filterCCA); action.addParameter(QLatin1String("vigCorrection"), prm.filterVIG); action.addParameter(QLatin1String("dstCorrection"), prm.filterDST); action.addParameter(QLatin1String("geoCorrection"), prm.filterGEO); action.addParameter(QLatin1String("cropFactor"), prm.cropFactor); action.addParameter(QLatin1String("focalLength"), prm.focalLength); action.addParameter(QLatin1String("aperture"), prm.aperture); action.addParameter(QLatin1String("subjectDistance"), prm.subjectDistance); action.addParameter(QLatin1String("cameraMake"), prm.cameraMake); action.addParameter(QLatin1String("cameraModel"), prm.cameraModel); action.addParameter(QLatin1String("lensModel"), prm.lensModel); return action; } void LensFunFilter::readParameters(const Digikam::FilterAction& action) { LensFunContainer prm = d->iface->settings(); prm.filterCCA = action.parameter(QLatin1String("ccaCorrection")).toBool(); prm.filterVIG = action.parameter(QLatin1String("vigCorrection")).toBool(); prm.filterDST = action.parameter(QLatin1String("dstCorrection")).toBool(); prm.filterGEO = action.parameter(QLatin1String("geoCorrection")).toBool(); prm.cropFactor = action.parameter(QLatin1String("cropFactor")).toDouble(); prm.focalLength = action.parameter(QLatin1String("focalLength")).toDouble(); prm.aperture = action.parameter(QLatin1String("aperture")).toDouble(); prm.subjectDistance = action.parameter(QLatin1String("subjectDistance")).toDouble(); prm.cameraMake = action.parameter(QLatin1String("cameraMake")).toString(); prm.cameraModel = action.parameter(QLatin1String("cameraModel")).toString(); prm.lensModel = action.parameter(QLatin1String("lensModel")).toString(); d->iface->setSettings(prm); } } // namespace Digikam // Restore warnings #if defined(Q_CC_GNU) # pragma GCC diagnostic pop #endif #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif diff --git a/core/libs/dimg/filters/nr/nrfilter.cpp b/core/libs/dimg/filters/nr/nrfilter.cpp index b8674a6809..832840b5b0 100644 --- a/core/libs/dimg/filters/nr/nrfilter.cpp +++ b/core/libs/dimg/filters/nr/nrfilter.cpp @@ -1,527 +1,527 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Wavelets Noise Reduction threaded image filter. * This filter work in YCrCb color space. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2008 by Marco Rossini * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "nrfilter.h" // C++ includes #include // Qt includes #include // krazy:exclude=includes // KDE includes #include // Local includes #include "dimg.h" #include "dcolor.h" namespace Digikam { NRContainer::NRContainer() { thresholds[0] = 1.2; // Y thresholds[1] = 1.2; // Cr thresholds[2] = 1.2; // Cb softness[0] = 0.9; // Y softness[1] = 0.9; // Cr softness[2] = 0.9; // Cb } NRContainer::~NRContainer() { } QDebug operator<<(QDebug dbg, const NRContainer& inf) { dbg.nospace() << "Y Threshold: " << inf.thresholds[0] << ", "; dbg.nospace() << "Y Softness: " << inf.softness[0] << ", "; dbg.nospace() << "Cb Threshold: " << inf.thresholds[1] << ", "; dbg.nospace() << "Cb Softness: " << inf.softness[1] << ", "; dbg.nospace() << "Cr Threshold: " << inf.thresholds[2] << ", "; dbg.nospace() << "Cr Softness: " << inf.softness[2]; return dbg.space(); } // ---------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN NRFilter::Private { public: explicit Private() { for (int c = 0 ; c < 3; ++c) { fimg[c] = nullptr; buffer[c] = nullptr; } } float* fimg[3]; float* buffer[3]; NRContainer settings; }; NRFilter::NRFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } NRFilter::NRFilter(DImg* const orgImage, QObject* const parent, const NRContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("NRFilter")), d(new Private) { d->settings = settings; initFilter(); } NRFilter::~NRFilter() { cancelFilter(); delete d; } FilterAction NRFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); for (int i = 0 ; i < 3 ; ++i) { action.addParameter(QString::fromLatin1("softness[%1]").arg(i), d->settings.softness[i]); action.addParameter(QString::fromLatin1("thresholds[%1]").arg(i), d->settings.thresholds[i]); } return action; } void NRFilter::readParameters(const FilterAction& action) { for (int i = 0 ; i < 3 ; ++i) { d->settings.softness[i] = action.parameter(QString::fromLatin1("softness[%1]").arg(i)).toDouble(); d->settings.thresholds[i] = action.parameter(QString::fromLatin1("thresholds[%1]").arg(i)).toDouble(); } } QString NRFilter::filterIdentifier() const { return FilterIdentifier(); } QString NRFilter::FilterIdentifier() { return QLatin1String("digikam:NoiseReductionFilter"); } QString NRFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Noise Reduction Filter")); } QList NRFilter::SupportedVersions() { return QList() << 1; } int NRFilter::CurrentVersion() { return 1; } void NRFilter::filterImage() { DColor col; int progress; int width = m_orgImage.width(); int height = m_orgImage.height(); float clip = m_orgImage.sixteenBit() ? 65535.0 : 255.0; // Allocate buffers. for (int c = 0 ; c < 3 ; ++c) { d->fimg[c] = new float[width * height]; } d->buffer[1] = new float[width * height]; d->buffer[2] = new float[width * height]; // Read the full image and convert pixel values to float [0,1]. int j = 0; for (int y = 0 ; runningFlag() && (y < height) ; ++y) { for (int x = 0 ; runningFlag() && (x < width) ; ++x) { col = m_orgImage.getPixelColor(x, y); d->fimg[0][j] = col.red() / clip; d->fimg[1][j] = col.green() / clip; d->fimg[2][j] = col.blue() / clip; ++j; } } postProgress(10); // do colour model conversion sRGB[0,1] -> YCrCb. if (runningFlag()) { srgb2ycbcr(d->fimg, width * height); } postProgress(20); // denoise the channels individually for (int c = 0 ; runningFlag() && (c < 3) ; ++c) { d->buffer[0] = d->fimg[c]; if (d->settings.thresholds[c] > 0.0) { waveletDenoise(d->buffer, width, height, d->settings.thresholds[c], d->settings.softness[c]); progress = (int)(30.0 + ((double)c * 60.0) / 4); if (progress % 5 == 0) { postProgress(progress); } } } // Retransform the image data to sRGB[0,1]. if (runningFlag()) { ycbcr2srgb(d->fimg, width * height); } postProgress(80); // clip the values for (int c = 0 ; runningFlag() && (c < 3) ; ++c) { for (int i = 0 ; i < width * height ; ++i) { d->fimg[c][i] = qBound(0.0F, d->fimg[c][i] * clip, clip); } } postProgress(90); // Write back the full image and convert pixel values from float [0,1]. j = 0; for (int y = 0 ; runningFlag() && (y < height) ; ++y) { for (int x = 0 ; x < width ; ++x) { col.setRed((int)(d->fimg[0][j] + 0.5)); col.setGreen((int)(d->fimg[1][j] + 0.5)); col.setBlue((int)(d->fimg[2][j] + 0.5)); col.setAlpha(m_orgImage.getPixelColor(x, y).alpha()); ++j; m_destImage.setPixelColor(x, y, col); } } postProgress(100); // Free buffers. for (int c = 0 ; c < 3 ; ++c) { delete [] d->fimg[c]; } delete [] d->buffer[1]; delete [] d->buffer[2]; } // -- Wavelets denoise methods ----------------------------------------------------------- void NRFilter::calculteStdevMultithreaded(const Args& prm) { for (uint i = prm.start ; runningFlag() && (i < prm.stop) ; ++i) { prm.fimg[*prm.hpass][i] -= prm.fimg[*prm.lpass][i]; if (prm.fimg[*prm.hpass][i] < *prm.thold && prm.fimg[*prm.hpass][i] > -*prm.thold) { if (prm.fimg[*prm.lpass][i] > 0.8) { prm.stdev[4] += prm.fimg[*prm.hpass][i] * prm.fimg[*prm.hpass][i]; prm.samples[4]++; } else if (prm.fimg[*prm.lpass][i] > 0.6) { prm.stdev[3] += prm.fimg[*prm.hpass][i] * prm.fimg[*prm.hpass][i]; prm.samples[3]++; } else if (prm.fimg[*prm.lpass][i] > 0.4) { prm.stdev[2] += prm.fimg[*prm.hpass][i] * prm.fimg[*prm.hpass][i]; prm.samples[2]++; } else if (prm.fimg[*prm.lpass][i] > 0.2) { prm.stdev[1] += prm.fimg[*prm.hpass][i] * prm.fimg[*prm.hpass][i]; prm.samples[1]++; } else { prm.stdev[0] += prm.fimg[*prm.hpass][i] * prm.fimg[*prm.hpass][i]; prm.samples[0]++; } } } } void NRFilter::thresholdingMultithreaded(const Args& prm) { for (uint i = prm.start ; runningFlag() && (i < prm.stop) ; ++i) { if (prm.fimg[*prm.lpass][i] > 0.8) { *prm.thold = prm.threshold * prm.stdev[4]; } else if (prm.fimg[*prm.lpass][i] > 0.6) { *prm.thold = prm.threshold * prm.stdev[3]; } else if (prm.fimg[*prm.lpass][i] > 0.4) { *prm.thold = prm.threshold * prm.stdev[2]; } else if (prm.fimg[*prm.lpass][i] > 0.2) { *prm.thold = prm.threshold * prm.stdev[1]; } else { *prm.thold = prm.threshold * prm.stdev[0]; } if (prm.fimg[*prm.hpass][i] < -*prm.thold) { prm.fimg[*prm.hpass][i] += *prm.thold - *prm.thold * prm.softness; } else if (prm.fimg[*prm.hpass][i] > *prm.thold) { prm.fimg[*prm.hpass][i] -= *prm.thold - *prm.thold * prm.softness; } else { prm.fimg[*prm.hpass][i] *= prm.softness; } if (*prm.hpass) { prm.fimg[0][i] += prm.fimg[*prm.hpass][i]; } } } void NRFilter::waveletDenoise(float* fimg[3], unsigned int width, unsigned int height, float threshold, double softness) { float thold = 0.0; uint lpass = 0; uint hpass = 0; double stdev[5] = { 0.0 }; uint samples[5] = { 0 }; uint size = width * height; QScopedArrayPointer temp(new float[qMax(width, height)]); QList vals = multithreadedSteps(size); QList > tasks; Args prm; prm.thold = &thold; prm.lpass = &lpass; prm.hpass = &hpass; prm.threshold = threshold; prm.softness = softness; prm.stdev = &stdev[0]; prm.samples = &samples[0]; prm.fimg = fimg; for (uint lev = 0 ; runningFlag() && (lev < 5) ; ++lev) { lpass = ((lev & 1) + 1); for (uint row = 0 ; runningFlag() && (row < height) ; ++row) { hatTransform(temp.data(), fimg[hpass] + row * width, 1, width, 1 << lev); for (uint col = 0 ; col < width ; ++col) { fimg[lpass][row * width + col] = temp[col] * 0.25; } } for (uint col = 0 ; runningFlag() && (col < width) ; ++col) { hatTransform(temp.data(), fimg[lpass] + col, width, height, 1 << lev); for (uint row = 0 ; row < height ; ++row) { fimg[lpass][row * width + col] = temp[row] * 0.25; } } thold = 5.0 / (1 << 6) * exp(-2.6 * sqrt(lev + 1.0)) * 0.8002 / exp(-2.6); // initialize stdev values for all intensities stdev[0] = stdev[1] = stdev[2] = stdev[3] = stdev[4] = 0.0; samples[0] = samples[1] = samples[2] = samples[3] = samples[4] = 0; // calculate stdevs for all intensities for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &NRFilter::calculteStdevMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); stdev[0] = sqrt(stdev[0] / (samples[0] + 1)); stdev[1] = sqrt(stdev[1] / (samples[1] + 1)); stdev[2] = sqrt(stdev[2] / (samples[2] + 1)); stdev[3] = sqrt(stdev[3] / (samples[3] + 1)); stdev[4] = sqrt(stdev[4] / (samples[4] + 1)); // do thresholding tasks.clear(); for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; tasks.append(QtConcurrent::run(this, &NRFilter::thresholdingMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); hpass = lpass; } for (uint i = 0 ; runningFlag() && (i < size) ; ++i) { fimg[0][i] = fimg[0][i] + fimg[lpass][i]; } } void NRFilter::hatTransform(float* const temp, float* const base, int st, int size, int sc) { int i; for (i = 0 ; i < sc ; ++i) { temp[i] = 2 * base[st * i] + base[st * (sc - i)] + base[st * (i + sc)]; } for ( ; i + sc < size ; ++i) { temp[i] = 2 * base[st * i] + base[st * (i - sc)] + base[st * (i + sc)]; } for ( ; i < size ; ++i) { temp[i] = 2 * base[st * i] + base[st * (i - sc)] + base[st * (2 * size - 2 - (i + sc))]; } } // -- Color Space conversion methods -------------------------------------------------- void NRFilter::srgb2ycbcr(float** const fimg, int size) { float y, cb, cr; for (int i = 0 ; i < size ; ++i) { y = 0.2990 * fimg[0][i] + 0.5870 * fimg[1][i] + 0.1140 * fimg[2][i]; cb = -0.1687 * fimg[0][i] - 0.3313 * fimg[1][i] + 0.5000 * fimg[2][i] + 0.5; cr = 0.5000 * fimg[0][i] - 0.4187 * fimg[1][i] - 0.0813 * fimg[2][i] + 0.5; fimg[0][i] = y; fimg[1][i] = cb; fimg[2][i] = cr; } } void NRFilter::ycbcr2srgb(float** const fimg, int size) { float r, g, b; for (int i = 0 ; i < size ; ++i) { r = fimg[0][i] + 1.40200 * (fimg[2][i] - 0.5); g = fimg[0][i] - 0.34414 * (fimg[1][i] - 0.5) - 0.71414 * (fimg[2][i] - 0.5); b = fimg[0][i] + 1.77200 * (fimg[1][i] - 0.5); fimg[0][i] = r; fimg[1][i] = g; fimg[2][i] = b; } } } // namespace Digikam diff --git a/core/libs/dimg/filters/sharp/refocusfilter.cpp b/core/libs/dimg/filters/sharp/refocusfilter.cpp index 4e2268777c..39f4c6310c 100644 --- a/core/libs/dimg/filters/sharp/refocusfilter.cpp +++ b/core/libs/dimg/filters/sharp/refocusfilter.cpp @@ -1,383 +1,383 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-25 * Description : Refocus threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009 by Matthias Welwarsky * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "refocusfilter.h" // C++ includes #include // Qt includes #include // krazy:exclude=includes // KDE includes #include // Local includes #include "dimg.h" #include "digikam_debug.h" #include "dcolor.h" #include "refocusmatrix.h" namespace Digikam { const int MAX_MATRIX_SIZE = 25; class Q_DECL_HIDDEN RefocusFilter::Private { public: explicit Private() : matrixSize(5), radius(0.9), gauss(0.0), correlation(0.5), noise(0.01) { } DImg preImage; int matrixSize; double radius; double gauss; double correlation; double noise; }; RefocusFilter::RefocusFilter(QObject* const parent) : DImgThreadedFilter(parent), d(new Private) { initFilter(); } RefocusFilter::RefocusFilter(DImg* const orgImage, QObject* const parent, int matrixSize, double radius, double gauss, double correlation, double noise) : DImgThreadedFilter(orgImage, parent, QLatin1String("Refocus")), d(new Private) { d->matrixSize = matrixSize; d->radius = radius; d->gauss = gauss; d->correlation = correlation; d->noise = noise; // initialize filter initFilter(); // initialize intermediate image d->preImage = DImg(orgImage->width() + 4 * MAX_MATRIX_SIZE, orgImage->height() + 4 * MAX_MATRIX_SIZE, orgImage->sixteenBit(), orgImage->hasAlpha()); } RefocusFilter::~RefocusFilter() { cancelFilter(); delete d; } QString RefocusFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Refocus")); } void RefocusFilter::filterImage() { bool sb = m_orgImage.sixteenBit(); bool a = m_orgImage.hasAlpha(); int w = m_orgImage.width(); int h = m_orgImage.height(); DImg img(w + 4 * MAX_MATRIX_SIZE, h + 4 * MAX_MATRIX_SIZE, sb, a); DImg tmp; // copy the original img.bitBltImage(&m_orgImage, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); // Create dummy top border tmp = m_orgImage.copy(0, 0, w, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, 2 * MAX_MATRIX_SIZE, 0); // Create dummy bottom border tmp = m_orgImage.copy(0, h - 2 * MAX_MATRIX_SIZE, w, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE + h); // Create dummy left border tmp = m_orgImage.copy(0, 0, 2 * MAX_MATRIX_SIZE, h); tmp.flip(DImg::HORIZONTAL); img.bitBltImage(&tmp, 0, 2 * MAX_MATRIX_SIZE); // Create dummy right border tmp = m_orgImage.copy(w - 2 * MAX_MATRIX_SIZE, 0, 2 * MAX_MATRIX_SIZE, h); tmp.flip(DImg::HORIZONTAL); img.bitBltImage(&tmp, w + 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); // Create dummy top/left corner tmp = m_orgImage.copy(0, 0, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::HORIZONTAL); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, 0, 0); // Create dummy top/right corner tmp = m_orgImage.copy(w - 2 * MAX_MATRIX_SIZE, 0, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::HORIZONTAL); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, w + 2 * MAX_MATRIX_SIZE, 0); // Create dummy bottom/left corner tmp = m_orgImage.copy(0, h - 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::HORIZONTAL); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, 0, h + 2 * MAX_MATRIX_SIZE); // Create dummy bottom/right corner tmp = m_orgImage.copy(w - 2 * MAX_MATRIX_SIZE, h - 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE); tmp.flip(DImg::HORIZONTAL); tmp.flip(DImg::VERTICAL); img.bitBltImage(&tmp, w + 2 * MAX_MATRIX_SIZE, h + 2 * MAX_MATRIX_SIZE); // run filter algorithm on the prepared copy refocusImage(img.bits(), img.width(), img.height(), img.sixteenBit(), d->matrixSize, d->radius, d->gauss, d->correlation, d->noise); // copy the result from intermediate image to final image m_destImage.bitBltImage(&d->preImage, 2 * MAX_MATRIX_SIZE, 2 * MAX_MATRIX_SIZE, w, h, 0, 0); } void RefocusFilter::refocusImage(uchar* const data, int width, int height, bool sixteenBit, int matrixSize, double radius, double gauss, double correlation, double noise) { CMat* matrix = nullptr; (void)matrix; // To prevent cppcheck warnings. // Compute matrix qCDebug(DIGIKAM_DIMG_LOG) << "RefocusFilter::Compute matrix..."; CMat circle, gaussian, convolution; RefocusMatrix::make_gaussian_convolution(gauss, &gaussian, matrixSize); RefocusMatrix::make_circle_convolution(radius, &circle, matrixSize); RefocusMatrix::init_c_mat(&convolution, matrixSize); RefocusMatrix::convolve_star_mat(&convolution, &gaussian, &circle); matrix = RefocusMatrix::compute_g_matrix(&convolution, matrixSize, correlation, noise, 0.0, true); RefocusMatrix::finish_c_mat(&convolution); RefocusMatrix::finish_c_mat(&gaussian); RefocusMatrix::finish_c_mat(&circle); // Apply deconvolution kernel to image. qCDebug(DIGIKAM_DIMG_LOG) << "RefocusFilter::Apply Matrix to image..."; Args prm; prm.orgData = data; prm.destData = d->preImage.bits(); prm.width = width; prm.height = height; prm.sixteenBit = sixteenBit; prm.matrix = matrix->data; prm.mat_size = 2 * matrixSize + 1; convolveImage(prm); // Clean up memory delete matrix; } void RefocusFilter::convolveImageMultithreaded(uint start, uint stop, uint y1, const Args& prm) { ushort* orgData16 = reinterpret_cast(prm.orgData); ushort* destData16 = reinterpret_cast(prm.destData); double valRed, valGreen, valBlue; uint x1, x2, y2; int index1, index2; const int imageSize = prm.width * prm.height; const int mat_offset = prm.mat_size / 2; for (x1 = start; runningFlag() && (x1 < stop); ++x1) { valRed = valGreen = valBlue = 0.0; if (!prm.sixteenBit) // 8 bits image. { uchar red, green, blue; uchar* ptr = nullptr; for (y2 = 0; runningFlag() && (y2 < prm.mat_size); ++y2) { int y2_matsize = y2 * prm.mat_size; for (x2 = 0; runningFlag() && (x2 < prm.mat_size); ++x2) { index1 = prm.width * (y1 + y2 - mat_offset) + x1 + x2 - mat_offset; if (index1 >= 0 && index1 < imageSize) { ptr = &prm.orgData[index1 * 4]; blue = ptr[0]; green = ptr[1]; red = ptr[2]; const double matrixValue = prm.matrix[y2_matsize + x2]; valRed += matrixValue * red; valGreen += matrixValue * green; valBlue += matrixValue * blue; } } } index2 = y1 * prm.width + x1; if (index2 >= 0 && index2 < imageSize) { // To get Alpha channel value from original (unchanged) memcpy(&prm.destData[index2 * 4], &prm.orgData[index2 * 4], 4); ptr = &prm.destData[index2 * 4]; // Overwrite RGB values to destination. ptr[0] = (uchar) CLAMP(valBlue, 0.0, 255.0); ptr[1] = (uchar) CLAMP(valGreen, 0.0, 255.0); ptr[2] = (uchar) CLAMP(valRed, 0.0, 255.0); } } else // 16 bits image. { ushort red, green, blue; ushort* ptr = nullptr; for (y2 = 0; runningFlag() && (y2 < prm.mat_size); ++y2) { int y2_matsize = y2 * prm.mat_size; for (x2 = 0; runningFlag() && (x2 < prm.mat_size); ++x2) { index1 = prm.width * (y1 + y2 - mat_offset) + x1 + x2 - mat_offset; if (index1 >= 0 && index1 < imageSize) { ptr = &orgData16[index1 * 4]; blue = ptr[0]; green = ptr[1]; red = ptr[2]; const double matrixValue = prm.matrix[y2_matsize + x2]; valRed += matrixValue * red; valGreen += matrixValue * green; valBlue += matrixValue * blue; } } } index2 = y1 * prm.width + x1; if (index2 >= 0 && index2 < imageSize) { // To get Alpha channel value from original (unchanged) memcpy(&destData16[index2 * 4], &orgData16[index2 * 4], 8); ptr = &destData16[index2 * 4]; // Overwrite RGB values to destination. ptr[0] = (ushort) CLAMP(valBlue, 0.0, 65535.0); ptr[1] = (ushort) CLAMP(valGreen, 0.0, 65535.0); ptr[2] = (ushort) CLAMP(valRed, 0.0, 65535.0); } } } } void RefocusFilter::convolveImage(const Args& prm) { int progress; QList vals = multithreadedSteps(prm.width); for (int y1 = 0; runningFlag() && (y1 < prm.height); ++y1) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &RefocusFilter::convolveImageMultithreaded, vals[j], vals[j+1], y1, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); // Update the progress bar in dialog. progress = (int)(((double)y1 * 100.0) / prm.height); if (progress % 5 == 0) { postProgress(progress); } } } FilterAction RefocusFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("correlation"), d->correlation); action.addParameter(QLatin1String("gauss"), d->gauss); action.addParameter(QLatin1String("matrixSize"), d->matrixSize); action.addParameter(QLatin1String("noise"), d->noise); action.addParameter(QLatin1String("radius"), d->radius); return action; } void RefocusFilter::readParameters(const Digikam::FilterAction& action) { d->correlation = action.parameter(QLatin1String("correlation")).toDouble(); d->gauss = action.parameter(QLatin1String("gauss")).toDouble(); d->matrixSize = action.parameter(QLatin1String("matrixSize")).toInt(); d->noise = action.parameter(QLatin1String("noise")).toDouble(); d->radius = action.parameter(QLatin1String("radius")).toDouble(); } int RefocusFilter::maxMatrixSize() { return MAX_MATRIX_SIZE; } } // namespace Digikam diff --git a/core/libs/dimg/filters/sharp/sharpenfilter.cpp b/core/libs/dimg/filters/sharp/sharpenfilter.cpp index e5e8d55a08..d2625fdb37 100644 --- a/core/libs/dimg/filters/sharp/sharpenfilter.cpp +++ b/core/libs/dimg/filters/sharp/sharpenfilter.cpp @@ -1,328 +1,328 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-17-07 * Description : A Sharpen threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * Original Sharpen algorithm copyright 2002 * by Daniel M. Duley from KImageEffect API. * * 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, 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. * * ============================================================ */ #define SQ2PI 2.50662827463100024161235523934010416269302368164062 #define Epsilon 1.0e-12 #include "sharpenfilter.h" // C++ includes #include #include // Qt includes #include // krazy:exclude=includes // KDE includes #include // Local includes #include "digikam_debug.h" namespace Digikam { SharpenFilter::SharpenFilter(QObject* const parent) : DImgThreadedFilter(parent) { m_radius = 0.0; m_sigma = 1.0; initFilter(); } SharpenFilter::SharpenFilter(DImg* const orgImage, QObject* const parent, double radius, double sigma) : DImgThreadedFilter(orgImage, parent, QLatin1String("Sharpen")) { m_radius = radius; m_sigma = sigma; initFilter(); } SharpenFilter::SharpenFilter(DImgThreadedFilter* const parentFilter, const DImg& orgImage, const DImg& destImage, int progressBegin, int progressEnd, double radius, double sigma) : DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd, parentFilter->filterName() + QLatin1String(": Sharpen")) { m_radius = radius; m_sigma = sigma; // We need to provide support for orgImage == destImage. // The algorithm does not support this out of the box, so use a temporary. if (orgImage.bits() == destImage.bits()) { m_destImage = DImg(destImage.width(), destImage.height(), destImage.sixteenBit()); } filterImage(); if (orgImage.bits() == destImage.bits()) { memcpy(destImage.bits(), m_destImage.bits(), m_destImage.numBytes()); } } SharpenFilter::~SharpenFilter() { cancelFilter(); } QString SharpenFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Sharpen")); } void SharpenFilter::filterImage() { sharpenImage(m_radius, m_sigma); } /** Function to apply the sharpen filter on an image*/ void SharpenFilter::sharpenImage(double radius, double sigma) { if (m_orgImage.isNull()) { qCWarning(DIGIKAM_DIMG_LOG) << "No image data available!"; return; } if (radius <= 0.0) { m_destImage = m_orgImage; return; } double alpha, normalize = 0.0; long i = 0, u, v; int kernelWidth = getOptimalKernelWidth(radius, sigma); int halfKernelWidth = kernelWidth / 2; if ((int)m_orgImage.width() < kernelWidth) { qCWarning(DIGIKAM_DIMG_LOG) << "Image is smaller than radius!"; return; } QScopedArrayPointer kernel(new double[kernelWidth * kernelWidth]); if (kernel.isNull()) { qCWarning(DIGIKAM_DIMG_LOG) << "Unable to allocate memory!"; return; } for (v = -halfKernelWidth; v <= halfKernelWidth; ++v) { for (u = -halfKernelWidth; u <= halfKernelWidth; ++u) { alpha = exp(-((double) u * u + v * v) / (2.0 * sigma * sigma)); kernel[i] = alpha / (2.0 * M_PI * sigma * sigma); normalize += kernel[i]; ++i; } } kernel[i / 2] = (-2.0) * normalize; convolveImage(kernelWidth, kernel.data()); } void SharpenFilter::convolveImageMultithreaded(const Args& prm) { double maxClamp = m_destImage.sixteenBit() ? 16777215.0 : 65535.0; double* k = nullptr; double red, green, blue, alpha; int mx, my, sx, sy, mcx, mcy; DColor color; for (uint x = prm.start ; runningFlag() && (x < prm.stop) ; ++x) { k = prm.normal_kernel; red = green = blue = alpha = 0; sy = prm.y - prm.halfKernelWidth; for (mcy = 0 ; runningFlag() && (mcy < prm.kernelWidth) ; ++mcy, ++sy) { my = sy < 0 ? 0 : sy > (int)m_destImage.height() - 1 ? m_destImage.height() - 1 : sy; sx = x + (-prm.halfKernelWidth); for (mcx = 0 ; runningFlag() && (mcx < prm.kernelWidth) ; ++mcx, ++sx) { mx = sx < 0 ? 0 : sx > (int)m_destImage.width() - 1 ? m_destImage.width() - 1 : sx; color = m_orgImage.getPixelColor(mx, my); red += (*k) * (color.red() * 257.0); green += (*k) * (color.green() * 257.0); blue += (*k) * (color.blue() * 257.0); alpha += (*k) * (color.alpha() * 257.0); ++k; } } red = red < 0.0 ? 0.0 : red > maxClamp ? maxClamp : red + 0.5; green = green < 0.0 ? 0.0 : green > maxClamp ? maxClamp : green + 0.5; blue = blue < 0.0 ? 0.0 : blue > maxClamp ? maxClamp : blue + 0.5; alpha = alpha < 0.0 ? 0.0 : alpha > maxClamp ? maxClamp : alpha + 0.5; m_destImage.setPixelColor(x, prm.y, DColor((int)(red / 257UL), (int)(green / 257UL), (int)(blue / 257UL), (int)(alpha / 257UL), m_destImage.sixteenBit())); } } bool SharpenFilter::convolveImage(const unsigned int order, const double* const kernel) { uint y; int progress; long i; double normalize = 0.0; Args prm; prm.kernelWidth = order; prm.halfKernelWidth = prm.kernelWidth / 2; if ((prm.kernelWidth % 2) == 0) { qCWarning(DIGIKAM_DIMG_LOG) << "Kernel width must be an odd number!"; return false; } QScopedArrayPointer normal_kernel(new double[prm.kernelWidth * prm.kernelWidth]); if (normal_kernel.isNull()) { qCWarning(DIGIKAM_DIMG_LOG) << "Unable to allocate memory!"; return false; } for (i = 0 ; i < (prm.kernelWidth * prm.kernelWidth) ; ++i) { normalize += kernel[i]; } if (fabs(normalize) <= Epsilon) { normalize = 1.0; } normalize = 1.0 / normalize; for (i = 0 ; i < (prm.kernelWidth * prm.kernelWidth) ; ++i) { normal_kernel[i] = normalize * kernel[i]; } prm.normal_kernel = normal_kernel.data(); QList vals = multithreadedSteps(m_destImage.width()); for (y = 0 ; runningFlag() && (y < m_destImage.height()) ; ++y) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { prm.start = vals[j]; prm.stop = vals[j+1]; prm.y = y; tasks.append(QtConcurrent::run(this, &SharpenFilter::convolveImageMultithreaded, prm )); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); progress = (int)(((double)y * 100.0) / m_destImage.height()); if (progress % 5 == 0) { postProgress(progress); } } return true; } int SharpenFilter::getOptimalKernelWidth(double radius, double sigma) { double normalize, value; long kernelWidth; long u; if (radius > 0.0) { return((int)(2.0 * ceil(radius) + 1.0)); } for (kernelWidth = 5; ;) { normalize = 0.0; for (u = (-kernelWidth / 2) ; u <= (kernelWidth / 2) ; ++u) { normalize += exp(-((double) u * u) / (2.0 * sigma * sigma)) / (SQ2PI * sigma); } u = kernelWidth / 2; value = exp(-((double) u * u) / (2.0 * sigma * sigma)) / (SQ2PI * sigma) / normalize; if ((long)(65535 * value) <= 0) { break; } kernelWidth += 2; } return ((int)kernelWidth - 2); } FilterAction SharpenFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("radius"), m_radius); action.addParameter(QLatin1String("sigma"), m_sigma); return action; } void SharpenFilter::readParameters(const Digikam::FilterAction& action) { m_radius = action.parameter(QLatin1String("radius")).toDouble(); m_sigma = action.parameter(QLatin1String("sigma")).toDouble(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/sharp/unsharpmaskfilter.cpp b/core/libs/dimg/filters/sharp/unsharpmaskfilter.cpp index ba44af05fd..99595b5e18 100644 --- a/core/libs/dimg/filters/sharp/unsharpmaskfilter.cpp +++ b/core/libs/dimg/filters/sharp/unsharpmaskfilter.cpp @@ -1,246 +1,246 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-17-07 * Description : A Sharpen threaded image filter. * * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2009 by Matthias Welwarsky * Copyright (C) 2010 by Martin Klapetek * * Original Sharpen algorithm copyright 2002 * by Daniel M. Duley from KImageEffect API. * * 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, 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. * * ============================================================ */ #include "unsharpmaskfilter.h" // C++ includes #include #include // Qt includes #include // krazy:exclude=includes // KDE includes #include // Local includes #include "dimg.h" #include "digikam_debug.h" #include "dcolor.h" #include "blurfilter.h" namespace Digikam { UnsharpMaskFilter::UnsharpMaskFilter(QObject* const parent) : DImgThreadedFilter(parent) { m_radius = 1; m_amount = 1.0; m_threshold = 0.05; m_luma = false; initFilter(); } UnsharpMaskFilter::UnsharpMaskFilter(DImg* const orgImage, QObject* const parent, double radius, double amount, double threshold, bool luma) : DImgThreadedFilter(orgImage, parent, QLatin1String("UnsharpMask")) { m_radius = radius; m_amount = amount; m_threshold = threshold; m_luma = luma; initFilter(); } UnsharpMaskFilter::~UnsharpMaskFilter() { cancelFilter(); } QString UnsharpMaskFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Unsharp Mask Tool")); } void UnsharpMaskFilter::unsharpMaskMultithreaded(uint start, uint stop, uint y) { long int zero = 0; double value = 0.0; DColor p; DColor q; long int quantum = m_destImage.sixteenBit() ? 65535 : 255; double quantumThreshold = quantum * m_threshold; int hp = 0, sp = 0, lp = 0, hq = 0, sq = 0, lq = 0; for (uint x = start ; runningFlag() && (x < stop) ; ++x) { p = m_orgImage.getPixelColor(x, y); q = m_destImage.getPixelColor(x, y); if (m_luma) { p.getHSL(&hp, &sp, &lp); q.getHSL(&hq, &sq, &lq); //luma channel value = (double)(lp) - (double)(lq); if (fabs(2.0 * value) < quantumThreshold) { value = (double)(lp); } else { value = (double)(lp) + value * m_amount; } q.setHSL(hp, sp, CLAMP(lround(value), zero, quantum), m_destImage.sixteenBit()); q.setAlpha(p.alpha()); } else { // Red channel. value = (double)(p.red()) - (double)(q.red()); if (fabs(2.0 * value) < quantumThreshold) { value = (double)(p.red()); } else { value = (double)(p.red()) + value * m_amount; } q.setRed(CLAMP(lround(value), zero, quantum)); // Green Channel. value = (double)(p.green()) - (double)(q.green()); if (fabs(2.0 * value) < quantumThreshold) { value = (double)(p.green()); } else { value = (double)(p.green()) + value * m_amount; } q.setGreen(CLAMP(lround(value), zero, quantum)); // Blue Channel. value = (double)(p.blue()) - (double)(q.blue()); if (fabs(2.0 * value) < quantumThreshold) { value = (double)(p.blue()); } else { value = (double)(p.blue()) + value * m_amount; } q.setBlue(CLAMP(lround(value), zero, quantum)); // Alpha Channel. value = (double)(p.alpha()) - (double)(q.alpha()); if (fabs(2.0 * value) < quantumThreshold) { value = (double)(p.alpha()); } else { value = (double)(p.alpha()) + value * m_amount; } q.setAlpha(CLAMP(lround(value), zero, quantum)); } m_destImage.setPixelColor(x, y, q); } } void UnsharpMaskFilter::filterImage() { int progress; if (m_orgImage.isNull()) { qCWarning(DIGIKAM_DIMG_LOG) << "No image data available!"; return; } BlurFilter(this, m_orgImage, m_destImage, 0, 10, (int)(m_radius*10.0)); QList vals = multithreadedSteps(m_destImage.width()); for (uint y = 0 ; runningFlag() && (y < m_destImage.height()) ; ++y) { QList > tasks; for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j) { tasks.append(QtConcurrent::run(this, &UnsharpMaskFilter::unsharpMaskMultithreaded, vals[j], vals[j+1], y)); } - foreach(QFuture t, tasks) + foreach (QFuture t, tasks) t.waitForFinished(); progress = (int)(10.0 + ((double)y * 90.0) / m_destImage.height()); if (progress % 5 == 0) { postProgress(progress); } } } FilterAction UnsharpMaskFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); action.addParameter(QLatin1String("amount"), m_amount); action.addParameter(QLatin1String("radius"), m_radius); action.addParameter(QLatin1String("threshold"), m_threshold); action.addParameter(QLatin1String("luma"), m_luma); return action; } void UnsharpMaskFilter::readParameters(const FilterAction& action) { m_amount = action.parameter(QLatin1String("amount")).toDouble(); m_radius = action.parameter(QLatin1String("radius")).toDouble(); m_threshold = action.parameter(QLatin1String("threshold")).toDouble(); m_luma = action.parameter(QLatin1String("luma")).toBool(); } } // namespace Digikam diff --git a/core/libs/dimg/history/dimagehistory.cpp b/core/libs/dimg/history/dimagehistory.cpp index dc79e2a1d9..da2634ad3f 100644 --- a/core/libs/dimg/history/dimagehistory.cpp +++ b/core/libs/dimg/history/dimagehistory.cpp @@ -1,796 +1,796 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-06-02 * Description : class for manipulating modifications changeset for non-destruct. editing * * Copyright (C) 2010 by Marcel Wiesweg * Copyright (C) 2010 by Martin Klapetek * * 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, 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. * * ============================================================ */ #include "dimagehistory.h" // Qt includes #include #include #include #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN DImageHistory::Private : public QSharedData { public: explicit Private() { } QList entries; }; // ----------------------------------------------------------------------------------------------- class Q_DECL_HIDDEN PrivateSharedNull : public QSharedDataPointer { public: PrivateSharedNull() : QSharedDataPointer(new DImageHistory::Private) { } }; Q_GLOBAL_STATIC(PrivateSharedNull, imageHistoryPrivSharedNull) // ----------------------------------------------------------------------------------------------- DImageHistory::DImageHistory() : d(*imageHistoryPrivSharedNull) { } DImageHistory::DImageHistory(const DImageHistory& other) { d = other.d; } DImageHistory::~DImageHistory() { } DImageHistory& DImageHistory::operator=(const DImageHistory& other) { d = other.d; return *this; } bool DImageHistory::isNull() const { return d == *imageHistoryPrivSharedNull; } bool DImageHistory::isEmpty() const { return d->entries.isEmpty(); } bool DImageHistory::isValid() const { if (d->entries.isEmpty()) { return false; } else if (d->entries.count() == 1 && d->entries.first().referredImages.count() == 1 && d->entries.first().referredImages.first().isCurrentFile()) { return false; } else { - foreach(const Entry& e, d->entries) + foreach (const Entry& e, d->entries) { if (!e.action.isNull()) { return true; } - foreach(const HistoryImageId& id, e.referredImages) + foreach (const HistoryImageId& id, e.referredImages) { if (id.isValid() && !id.isCurrentFile()) { return true; } } } } return false; } int DImageHistory::size() const { return d->entries.size(); } static bool operator==(const DImageHistory::Entry& e1, const DImageHistory::Entry& e2) { return e1.action == e2.action && e1.referredImages == e2.referredImages; } bool DImageHistory::operator==(const DImageHistory& other) const { return d->entries == other.d->entries; } bool DImageHistory::operator<(const Digikam::DImageHistory& other) const { if (d->entries.size() < other.size()) { return true; } return false; } bool DImageHistory::operator>(const Digikam::DImageHistory& other) const { if (d->entries.size() > other.size()) { return true; } return false; } QList &DImageHistory::entries() { return d->entries; } const QList &DImageHistory::entries() const { return d->entries; } DImageHistory::Entry& DImageHistory::operator[](int i) { return d->entries[i]; } const DImageHistory::Entry& DImageHistory::operator[](int i) const { return d->entries.at(i); } DImageHistory& DImageHistory::operator<<(const FilterAction& action) { if (action.isNull()) { return *this; } Entry entry; entry.action = action; d->entries << entry; //qCDebug(DIGIKAM_DIMG_LOG) << "Entry added, total count " << d->entries.count(); return *this; } DImageHistory& DImageHistory::operator<<(const HistoryImageId& id) { appendReferredImage(id); return *this; } void DImageHistory::appendReferredImage(const HistoryImageId& id) { insertReferredImage(d->entries.size() - 1, id); } void DImageHistory::insertReferredImage(int index, const HistoryImageId& id) { if (!id.isValid()) { qCWarning(DIGIKAM_DIMG_LOG) << "Attempt to add an invalid HistoryImageId"; return; } index = qBound(0, index, d->entries.size() - 1); if (id.isCurrentFile()) { // enforce to have exactly one Current id adjustReferredImages(); } if (d->entries.isEmpty()) { d->entries << Entry(); } d->entries[index].referredImages << id; } void DImageHistory::removeLast() { if (!d->entries.isEmpty()) { d->entries.removeLast(); } } const FilterAction& DImageHistory::action(int i) const { return d->entries.at(i).action; } QList DImageHistory::allActions() const { QList actions; - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { actions << entry.action; } } return actions; } int DImageHistory::actionCount() const { int count = 0; - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { ++count; } } return count; } bool DImageHistory::hasActions() const { - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { if (!entry.action.isNull()) { return true; } } return false; } QList &DImageHistory::referredImages(int i) { return d->entries[i].referredImages; } const QList &DImageHistory::referredImages(int i) const { return d->entries.at(i).referredImages; } QList DImageHistory::allReferredImages() const { QList ids; - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { ids << entry.referredImages; } return ids; } bool DImageHistory::hasReferredImages() const { - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { if (!entry.referredImages.isEmpty()) { return true; } } return false; } bool DImageHistory::hasReferredImageOfType(HistoryImageId::Type type) const { - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { - foreach(const HistoryImageId& id, entry.referredImages) + foreach (const HistoryImageId& id, entry.referredImages) { if (id.m_type == type) { return true; } } } return false; } bool DImageHistory::hasCurrentReferredImage() const { return hasReferredImageOfType(HistoryImageId::Current); } bool DImageHistory::hasOriginalReferredImage() const { return hasReferredImageOfType(HistoryImageId::Original); } QList DImageHistory::referredImagesOfType(HistoryImageId::Type type) const { QList ids; - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { - foreach(const HistoryImageId& id, entry.referredImages) + foreach (const HistoryImageId& id, entry.referredImages) { if (id.m_type == type) { ids << id; } } } return ids; } HistoryImageId DImageHistory::currentReferredImage() const { - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { - foreach(const HistoryImageId& id, entry.referredImages) + foreach (const HistoryImageId& id, entry.referredImages) { if (id.isCurrentFile()) { return id; } } } return HistoryImageId(); } HistoryImageId DImageHistory::originalReferredImage() const { - foreach(const Entry& entry, d->entries) + foreach (const Entry& entry, d->entries) { - foreach(const HistoryImageId& id, entry.referredImages) + foreach (const HistoryImageId& id, entry.referredImages) { if (id.isOriginalFile()) { return id; } } } return HistoryImageId(); } void DImageHistory::clearReferredImages() { for (int i=0; ientries.size(); ++i) { d->entries[i].referredImages.clear(); } } void DImageHistory::adjustReferredImages() { for (int i=0; ientries.size(); ++i) { Entry& entry = d->entries[i]; for (int e=0; eentries.size(); ++i) { Entry& entry = d->entries[i]; for (int e=0; eentries.size(); ++i) { Entry& entry = d->entries[i]; for (int e=0; eentries.size(); ++i) { Entry& entry = d->entries[i]; for (int e=0; e& params = step.action.parameters(); if (!params.isEmpty()) { QList keys = params.keys(); std::sort(keys.begin(), keys.end()); - foreach(const QString& key, keys) + foreach (const QString& key, keys) { QHash::const_iterator it; for (it = params.find(key); it != params.end() && it.key() == key; ++it) { stream.writeStartElement(QLatin1String("param")); stream.writeAttribute(QLatin1String("name"), key); stream.writeAttribute(QLatin1String("value"), it.value().toString()); stream.writeEndElement(); //param } } } stream.writeEndElement(); //params stream.writeEndElement(); //filter } if (!step.referredImages.isEmpty()) { - foreach(const HistoryImageId& imageId, step.referredImages) + foreach (const HistoryImageId& imageId, step.referredImages) { if (!imageId.isValid()) { continue; } if (imageId.isCurrentFile()) { continue; } stream.writeStartElement(QLatin1String("file")); if (!imageId.m_uuid.isNull()) { stream.writeAttribute(QLatin1String("uuid"), imageId.m_uuid); } if (imageId.isOriginalFile()) { stream.writeAttribute(QLatin1String("type"), QLatin1String("original")); } else if (imageId.isSourceFile()) { stream.writeAttribute(QLatin1String("type"), QLatin1String("source")); } stream.writeStartElement(QLatin1String("fileParams")); if (!imageId.m_fileName.isNull()) { stream.writeAttribute(QLatin1String("fileName"), imageId.m_fileName); } if (!imageId.m_filePath.isNull()) { stream.writeAttribute(QLatin1String("filePath"), imageId.m_filePath); } if (!imageId.m_uniqueHash.isNull()) { stream.writeAttribute(QLatin1String("fileHash"), imageId.m_uniqueHash); } if (imageId.m_fileSize) { stream.writeAttribute(QLatin1String("fileSize"), QString::number(imageId.m_fileSize)); } if (imageId.isOriginalFile() && !imageId.m_creationDate.isNull()) { stream.writeAttribute(QLatin1String("creationDate"), imageId.m_creationDate.toString(Qt::ISODate)); } stream.writeEndElement(); //fileParams stream.writeEndElement(); //file } } } stream.writeEndElement(); //history stream.writeEndDocument(); //qCDebug(DIGIKAM_DIMG_LOG) << xmlHistory; return xmlHistory; } DImageHistory DImageHistory::fromXml(const QString& xml) //DImageHistory { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing image history XML"; DImageHistory h; if (xml.isEmpty()) { return h; } QXmlStreamReader stream(xml); if (!stream.readNextStartElement()) { return h; } if (stream.name() != QLatin1String("history")) { return h; } QString originalUUID; QDateTime originalCreationDate; while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("file")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing file tag"; HistoryImageId imageId(stream.attributes().value(QLatin1String("uuid")).toString()); if (stream.attributes().value(QLatin1String("type")) == QLatin1String("original")) { imageId.m_type = HistoryImageId::Original; } else if (stream.attributes().value(QLatin1String("type")) == QLatin1String("source")) { imageId.m_type = HistoryImageId::Source; } else { imageId.m_type = HistoryImageId::Intermediate; } while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("fileParams")) { imageId.setFileName(stream.attributes().value(QLatin1String("fileName")).toString()); imageId.setPath(stream.attributes().value(QLatin1String("filePath")).toString()); QString date = stream.attributes().value(QLatin1String("creationDate")).toString(); if (!date.isNull()) { imageId.setCreationDate(QDateTime::fromString(date, Qt::ISODate)); } QString size = stream.attributes().value(QLatin1String("fileSize")).toString(); if (stream.attributes().hasAttribute(QLatin1String("fileHash")) && !size.isNull()) { imageId.setUniqueHash(stream.attributes().value(QLatin1String("fileHash")).toString(), size.toInt()); } stream.skipCurrentElement(); } else { stream.skipCurrentElement(); } } if (imageId.isOriginalFile()) { originalUUID = imageId.m_uuid; originalCreationDate = imageId.m_creationDate; } else { imageId.m_originalUUID = originalUUID; if (imageId.m_creationDate.isNull()) { imageId.m_creationDate = originalCreationDate; } } if (imageId.isValid()) { h << imageId; } } else if (stream.name() == QLatin1String("filter")) { //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing filter tag"; FilterAction::Category c = FilterAction::ComplexFilter; QStringRef categoryString = stream.attributes().value(QLatin1String("filterCategory")); if (categoryString == QLatin1String("reproducible")) { c = FilterAction::ReproducibleFilter; } else if (categoryString == QLatin1String("complex")) { c = FilterAction::ComplexFilter; } else if (categoryString == QLatin1String("documentedHistory")) { c = FilterAction::DocumentedHistory; } FilterAction action(stream.attributes().value(QLatin1String("filterName")).toString(), stream.attributes().value(QLatin1String("filterVersion")).toString().toInt(), c); action.setDisplayableName(stream.attributes().value(QLatin1String("filterDisplayName")).toString()); if (stream.attributes().value(QLatin1String("branch")) == QLatin1String("true")) { action.addFlag(FilterAction::ExplicitBranch); } while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("params")) { while (stream.readNextStartElement()) { if (stream.name() == QLatin1String("param") && stream.attributes().hasAttribute(QLatin1String("name"))) { action.addParameter(stream.attributes().value(QLatin1String("name")).toString(), stream.attributes().value(QLatin1String("value")).toString()); stream.skipCurrentElement(); } else { stream.skipCurrentElement(); } } } else { stream.skipCurrentElement(); } } h << action; } else { stream.skipCurrentElement(); } } if (stream.hasError()) { //TODO: error handling qCDebug(DIGIKAM_DIMG_LOG) << "An error occurred during parsing: " << stream.errorString(); } //qCDebug(DIGIKAM_DIMG_LOG) << "Parsing done"; return h; } } // namespace digikam diff --git a/core/libs/dplugins/webservices/o2/src/o1.cpp b/core/libs/dplugins/webservices/o2/src/o1.cpp index ac3c36ebb6..9c883a9baf 100644 --- a/core/libs/dplugins/webservices/o2/src/o1.cpp +++ b/core/libs/dplugins/webservices/o2/src/o1.cpp @@ -1,421 +1,421 @@ #include #include #include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #if QT_VERSION >= 0x050100 #include #endif #include "o1.h" #include "o2replyserver.h" #include "o0globals.h" #include "o0settingsstore.h" O1::O1(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) { setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); manager_ = manager ? manager : new QNetworkAccessManager(this); qRegisterMetaType("QNetworkReply::NetworkError"); setCallbackUrl(O2_CALLBACK_URL); } QByteArray O1::userAgent() const { return userAgent_; } void O1::setUserAgent(const QByteArray &v) { userAgent_ = v; } QUrl O1::requestTokenUrl() { return requestTokenUrl_; } void O1::setRequestTokenUrl(const QUrl &v) { requestTokenUrl_ = v; Q_EMIT requestTokenUrlChanged(); } QList O1::requestParameters() { return requestParameters_; } void O1::setRequestParameters(const QList &v) { requestParameters_ = v; } QString O1::callbackUrl() { return callbackUrl_; } void O1::setCallbackUrl(const QString &v) { callbackUrl_ = v; } QUrl O1::authorizeUrl() { return authorizeUrl_; } void O1::setAuthorizeUrl(const QUrl &value) { authorizeUrl_ = value; Q_EMIT authorizeUrlChanged(); } QUrl O1::accessTokenUrl() { return accessTokenUrl_; } void O1::setAccessTokenUrl(const QUrl &value) { accessTokenUrl_ = value; Q_EMIT accessTokenUrlChanged(); } QString O1::signatureMethod() { return signatureMethod_; } void O1::setSignatureMethod(const QString &value) { qDebug() << "O1::setSignatureMethod: " << value; signatureMethod_ = value; } void O1::unlink() { qDebug() << "O1::unlink"; setLinked(false); setToken(""); setTokenSecret(""); setExtraTokens(QVariantMap()); Q_EMIT linkingSucceeded(); } #if QT_VERSION < 0x050100 /// Calculate the HMAC variant of SHA1 hash. /// @author http://qt-project.org/wiki/HMAC-SHA1. /// @copyright Creative Commons Attribution-ShareAlike 2.5 Generic. static QByteArray hmacSha1(QByteArray key, QByteArray baseString) { int blockSize = 64; if (key.length() > blockSize) { key = QCryptographicHash::hash(key, QCryptographicHash::Sha1); } QByteArray innerPadding(blockSize, char(0x36)); QByteArray outerPadding(blockSize, char(0x5c)); for (int i = 0; i < key.length(); i++) { innerPadding[i] = innerPadding[i] ^ key.at(i); outerPadding[i] = outerPadding[i] ^ key.at(i); } QByteArray total = outerPadding; QByteArray part = innerPadding; part.append(baseString); total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1)); QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1); return hashed.toBase64(); } #endif /// Get HTTP operation name. static QString getOperationName(QNetworkAccessManager::Operation op) { switch (op) { case QNetworkAccessManager::GetOperation: return "GET"; case QNetworkAccessManager::PostOperation: return "POST"; case QNetworkAccessManager::PutOperation: return "PUT"; case QNetworkAccessManager::DeleteOperation: return "DEL"; default: return ""; } } /// Build a concatenated/percent-encoded string from a list of headers. QByteArray O1::encodeHeaders(const QList &headers) { return QUrl::toPercentEncoding(createQueryParameters(headers)); } /// Build a base string for signing. QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) { QByteArray base; // Initialize base string with the operation name (e.g. "GET") and the base URL base.append(getOperationName(op).toUtf8() + "&"); base.append(QUrl::toPercentEncoding(url.toString(QUrl::RemoveQuery)) + "&"); // Append a sorted+encoded list of all request parameters to the base string QList headers(oauthParams); headers.append(otherParams); std::sort(headers.begin(), headers.end()); base.append(encodeHeaders(headers)); return base; } QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) { QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op); QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret); #if QT_VERSION >= 0x050100 return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64(); #else return hmacSha1(secret, baseString); #endif } QByteArray O1::buildAuthorizationHeader(const QList &oauthParams) { bool first = true; QByteArray ret("OAuth "); QList headers(oauthParams); std::sort(headers.begin(), headers.end()); foreach (O0RequestParameter h, headers) { if (first) { first = false; } else { ret.append(","); } ret.append(h.name); ret.append("=\""); ret.append(QUrl::toPercentEncoding(h.value)); ret.append("\""); } return ret; } void O1::decorateRequest(QNetworkRequest &req, const QList &oauthParams) { req.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); if (!userAgent_.isEmpty()) { #if QT_VERSION >= 0x050000 req.setHeader(QNetworkRequest::UserAgentHeader, userAgent_); #else req.setRawHeader("User-Agent", userAgent_); #endif } } QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { QByteArray signature; if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) { signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); } else if (signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) { signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1(); } return signature; } void O1::link() { qDebug() << "O1::link"; // Create the reply server if it doesn't exist // and we don't use an external web interceptor if(!useExternalWebInterceptor_) { if(replyServer_ == NULL) { replyServer_ = new O2ReplyServer(this); connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); } } if (linked()) { qDebug() << "O1::link: Linked already"; Q_EMIT linkingSucceeded(); return; } setLinked(false); setToken(""); setTokenSecret(""); setExtraTokens(QVariantMap()); if (!useExternalWebInterceptor_) { // Start reply server if (!replyServer_->isListening()) replyServer_->listen(QHostAddress::Any, localPort()); } // Get any query parameters for the request #if QT_VERSION >= 0x050000 QUrlQuery requestData; #else QUrl requestData = requestTokenUrl(); #endif O0RequestParameter param("", ""); - foreach(param, requestParameters()) + foreach (param, requestParameters()) requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value))); // Get the request url and add parameters #if QT_VERSION >= 0x050000 QUrl requestUrl = requestTokenUrl(); requestUrl.setQuery(requestData); // Create request QNetworkRequest request(requestUrl); #else // Create request QNetworkRequest request(requestData); #endif // Create initial token request QList headers; headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, requestParameters(), QNetworkAccessManager::PostOperation))); // Clear request token requestToken_.clear(); requestTokenSecret_.clear(); // Post request decorateRequest(request, headers); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); } void O1::onTokenRequestError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll(); Q_EMIT linkingFailed(); } void O1::onTokenRequestFinished() { qDebug() << "O1::onTokenRequestFinished"; QNetworkReply *reply = qobject_cast(sender()); qDebug() << QString( "Request: %1" ).arg(reply->request().url().toString()); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { qWarning() << "O1::onTokenRequestFinished: " << reply->errorString(); return; } // Get request token and secret QByteArray data = reply->readAll(); QMap response = parseResponse(data); requestToken_ = response.value(O2_OAUTH_TOKEN, ""); requestTokenSecret_ = response.value(O2_OAUTH_TOKEN_SECRET, ""); setToken(requestToken_); setTokenSecret(requestTokenSecret_); // Checking for "oauth_callback_confirmed" is present and set to true QString oAuthCbConfirmed = response.value(O2_OAUTH_CALLBACK_CONFIRMED, "false"); if (requestToken_.isEmpty() || requestTokenSecret_.isEmpty() || (oAuthCbConfirmed == "false")) { qWarning() << "O1::onTokenRequestFinished: No oauth_token, oauth_token_secret or oauth_callback_confirmed in response :" << data; Q_EMIT linkingFailed(); return; } // Continue authorization flow in the browser QUrl url(authorizeUrl()); #if QT_VERSION < 0x050000 url.addQueryItem(O2_OAUTH_TOKEN, requestToken_); url.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1()); #else QUrlQuery query(url); query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1()); url.setQuery(query); #endif Q_EMIT openBrowser(url); } void O1::onVerificationReceived(QMap params) { qDebug() << "O1::onVerificationReceived"; Q_EMIT closeBrowser(); verifier_ = params.value(O2_OAUTH_VERFIER, ""); if (params.value(O2_OAUTH_TOKEN) == requestToken_) { // Exchange request token for access token exchangeToken(); } else { qWarning() << "O1::onVerificationReceived: oauth_token missing or doesn't match"; Q_EMIT linkingFailed(); } } void O1::exchangeToken() { qDebug() << "O1::exchangeToken"; // Create token exchange request QNetworkRequest request(accessTokenUrl()); QList oauthParams; oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0")); oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce())); oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList(), QNetworkAccessManager::PostOperation))); // Post request decorateRequest(request, oauthParams); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); } void O1::onTokenExchangeError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll(); Q_EMIT linkingFailed(); } void O1::onTokenExchangeFinished() { qDebug() << "O1::onTokenExchangeFinished"; QNetworkReply *reply = qobject_cast(sender()); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { qWarning() << "O1::onTokenExchangeFinished: " << reply->errorString(); return; } // Get access token and secret QByteArray data = reply->readAll(); qWarning() << "data: " << QString(data); QMap response = parseResponse(data); if (response.contains(O2_OAUTH_TOKEN) && response.contains(O2_OAUTH_TOKEN_SECRET)) { setToken(response.take(O2_OAUTH_TOKEN)); setTokenSecret(response.take(O2_OAUTH_TOKEN_SECRET)); // Set extra tokens if any if (!response.isEmpty()) { QVariantMap extraTokens; foreach (QString key, response.keys()) { extraTokens.insert(key, response.value(key)); } setExtraTokens(extraTokens); } setLinked(true); Q_EMIT linkingSucceeded(); } else { qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data; Q_EMIT linkingFailed(); } } QMap O1::parseResponse(const QByteArray &response) { QMap ret; foreach (QByteArray param, response.split('&')) { QList kv = param.split('='); if (kv.length() == 2) { ret.insert(QUrl::fromPercentEncoding(kv[0]), QUrl::fromPercentEncoding(kv[1])); } } return ret; } QByteArray O1::nonce() { static bool firstTime = true; if (firstTime) { firstTime = false; qsrand(QTime::currentTime().msec()); } QString u = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); u.append(QString::number(qrand())); return u.toLatin1(); } diff --git a/core/libs/dplugins/webservices/wsselectuserdlg.cpp b/core/libs/dplugins/webservices/wsselectuserdlg.cpp index 578dd56da3..b915c31160 100644 --- a/core/libs/dplugins/webservices/wsselectuserdlg.cpp +++ b/core/libs/dplugins/webservices/wsselectuserdlg.cpp @@ -1,163 +1,163 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2015-16-05 * Description : a dialog to select user for Web Service tools * * Copyright (C) 2015 by Shourya Singh Gupta * Copyright (C) 2016-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "wsselectuserdlg.h" // Qt includes #include #include #include #include #include #include // KDE includes #include #include #include namespace Digikam { class Q_DECL_HIDDEN WSSelectUserDlg::Private { public: explicit Private() { userComboBox = nullptr; label = nullptr; okButton = nullptr; } QComboBox* userComboBox; QLabel* label; QPushButton* okButton; QString userName; QString serviceName; }; WSSelectUserDlg::WSSelectUserDlg(QWidget* const parent, const QString& serviceName) : QDialog(parent), d(new Private) { d->serviceName = serviceName; setWindowTitle(i18n("Account Selector")); setModal(true); QDialogButtonBox* const buttonBox = new QDialogButtonBox(); QPushButton* const buttonNewAccount = new QPushButton(buttonBox); buttonNewAccount->setText(i18n("Add another account")); buttonNewAccount->setIcon(QIcon::fromTheme(QLatin1String("network-workgroup"))); buttonBox->addButton(buttonNewAccount, QDialogButtonBox::AcceptRole); buttonBox->addButton(QDialogButtonBox::Ok); buttonBox->addButton(QDialogButtonBox::Close); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); d->okButton = buttonBox->button(QDialogButtonBox::Ok); if (d->serviceName == QLatin1String("23")) { setWindowIcon(QIcon::fromTheme(QLatin1String("hq"))); } else { setWindowIcon(QIcon::fromTheme(QLatin1String("dk-flickr"))); } d->userName = QString(); d->label = new QLabel(this); d->label->setText(i18n("Choose the %1 account to use for exporting images:", d->serviceName)); d->userComboBox = new QComboBox(this); QVBoxLayout* const mainLayout = new QVBoxLayout(this); mainLayout->addWidget(d->label); mainLayout->addWidget(d->userComboBox); mainLayout->addWidget(buttonBox); setLayout(mainLayout); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(slotOkClicked())); connect(buttonNewAccount, SIGNAL(clicked()), this, SLOT(slotNewAccountClicked())); } WSSelectUserDlg::~WSSelectUserDlg() { delete d->userComboBox; delete d->label; delete d; } void WSSelectUserDlg::reactivate() { KConfig config; d->userComboBox->clear(); - foreach(const QString& group, config.groupList()) + foreach (const QString& group, config.groupList()) { if (!(group.contains(d->serviceName))) continue; KConfigGroup grp = config.group(group); if (QString::compare(grp.readEntry(QLatin1String("username")), QString(), Qt::CaseInsensitive) == 0) continue; d->userComboBox->addItem(grp.readEntry(QLatin1String("username"))); } d->okButton->setEnabled(d->userComboBox->count() > 0); exec(); } void WSSelectUserDlg::slotOkClicked() { d->userName = d->userComboBox->currentText(); } void WSSelectUserDlg::slotNewAccountClicked() { d->userName = QString(); } QString WSSelectUserDlg::getUserName() const { return d->userName; } } // namespace Digikam diff --git a/core/libs/dplugins/widgets/ditemslist.cpp b/core/libs/dplugins/widgets/ditemslist.cpp index 6c1d62d322..a2456509aa 100644 --- a/core/libs/dplugins/widgets/ditemslist.cpp +++ b/core/libs/dplugins/widgets/ditemslist.cpp @@ -1,1326 +1,1326 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-05-21 * Description : widget to display a list of items * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2008-2010 by Andi Clemens * Copyright (C) 2009-2010 by Luka Renko * * 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, 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. * * ============================================================ */ #include "ditemslist.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "drawdecoder.h" #include "imagedialog.h" #include "digikam_debug.h" #include "dlayoutbox.h" #include "dfiledialog.h" #include "thumbnailloadthread.h" #include "dworkingpixmap.h" namespace Digikam { const int DEFAULTSIZE = 48; class Q_DECL_HIDDEN DItemsListViewItem::Private { public: explicit Private() { rating = -1; view = nullptr; state = Waiting; hasThumb = false; } bool hasThumb; // True if thumbnails is a real photo thumbs int rating; // Image Rating from host. QString comments; // Image comments from host. QStringList tags; // List of keywords from host. QUrl url; // Image url provided by host. QPixmap thumb; // Image thumbnail. DItemsListView* view; State state; }; DItemsListViewItem::DItemsListViewItem(DItemsListView* const view, const QUrl& url) : QTreeWidgetItem(view), d(new Private) { setUrl(url); setRating(-1); setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); d->view = view; int iconSize = d->view->iconSize().width(); setThumb(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(iconSize, iconSize, QIcon::Disabled), false); /* qCDebug(DIGIKAM_GENERAL_LOG) << "Creating new ImageListViewItem with url " << d->url << " for list view " << d->view; */ } DItemsListViewItem::~DItemsListViewItem() { delete d; } bool DItemsListViewItem::hasValidThumbnail() const { return d->hasThumb; } void DItemsListViewItem::updateInformation() { if (d->view->iface()) { DItemInfo info(d->view->iface()->itemInfo(d->url)); setComments(info.comment()); setTags(info.keywords()); setRating(info.rating()); } } void DItemsListViewItem::setUrl(const QUrl& url) { d->url = url; setText(DItemsListView::Filename, d->url.fileName()); } QUrl DItemsListViewItem::url() const { return d->url; } void DItemsListViewItem::setComments(const QString& comments) { d->comments = comments; } QString DItemsListViewItem::comments() const { return d->comments; } void DItemsListViewItem::setTags(const QStringList& tags) { d->tags = tags; } QStringList DItemsListViewItem::tags() const { return d->tags; } void DItemsListViewItem::setRating(int rating) { d->rating = rating; } int DItemsListViewItem::rating() const { return d->rating; } void DItemsListViewItem::setPixmap(const QPixmap& pix) { QIcon icon = QIcon(pix); // We make sure the preview icon stays the same regardless of the role icon.addPixmap(pix, QIcon::Selected, QIcon::On); icon.addPixmap(pix, QIcon::Selected, QIcon::Off); icon.addPixmap(pix, QIcon::Active, QIcon::On); icon.addPixmap(pix, QIcon::Active, QIcon::Off); icon.addPixmap(pix, QIcon::Normal, QIcon::On); icon.addPixmap(pix, QIcon::Normal, QIcon::Off); setIcon(DItemsListView::Thumbnail, icon); } void DItemsListViewItem::setThumb(const QPixmap& pix, bool hasThumb) { /* qCDebug(DIGIKAM_GENERAL_LOG) << "Received new thumbnail for url " << d->url << ". My view is " << d->view; */ if (!d->view) { qCCritical(DIGIKAM_GENERAL_LOG) << "This item do not have a tree view. " << "This should never happen!"; return; } int iconSize = qMax(d->view->iconSize().width(), d->view->iconSize().height()); QPixmap pixmap(iconSize + 2, iconSize + 2); pixmap.fill(Qt::transparent); QPainter p(&pixmap); p.drawPixmap((pixmap.width() / 2) - (pix.width() / 2), (pixmap.height() / 2) - (pix.height() / 2), pix); d->thumb = pixmap; setPixmap(d->thumb); d->hasThumb = hasThumb; } void DItemsListViewItem::setProgressAnimation(const QPixmap& pix) { QPixmap overlay = d->thumb; QPixmap mask(overlay.size()); mask.fill(QColor(128, 128, 128, 192)); QPainter p(&overlay); p.drawPixmap(0, 0, mask); p.drawPixmap((overlay.width() / 2) - (pix.width() / 2), (overlay.height() / 2) - (pix.height() / 2), pix); setPixmap(overlay); } void DItemsListViewItem::setProcessedIcon(const QIcon& icon) { setIcon(DItemsListView::Filename, icon); // reset thumbnail back to no animation pix setPixmap(d->thumb); } void DItemsListViewItem::setState(State state) { d->state = state; } DItemsListViewItem::State DItemsListViewItem::state() const { return d->state; } DItemsListView* DItemsListViewItem::view() const { return d->view; } // --------------------------------------------------------------------------- DItemsListView::DItemsListView(DItemsList* const parent) : QTreeWidget(parent) { setup(DEFAULTSIZE); } DItemsListView::DItemsListView(int iconSize, DItemsList* const parent) : QTreeWidget(parent) { setup(iconSize); } DItemsListView::~DItemsListView() { } DInfoInterface* DItemsListView::iface() const { DItemsList* const p = dynamic_cast(parent()); if (p) { return p->iface(); } return nullptr; } void DItemsListView::setup(int iconSize) { m_iconSize = iconSize; setIconSize(QSize(m_iconSize, m_iconSize)); setAlternatingRowColors(true); setSelectionMode(QAbstractItemView::ExtendedSelection); enableDragAndDrop(true); setSortingEnabled(false); setAllColumnsShowFocus(true); setRootIsDecorated(false); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setColumnCount(8); setHeaderLabels(QStringList() << i18n("Thumbnail") << i18n("File Name") << i18n("User1") << i18n("User2") << i18n("User3") << i18n("User4") << i18n("User5") << i18n("User6")); hideColumn(User1); hideColumn(User2); hideColumn(User3); hideColumn(User4); hideColumn(User5); hideColumn(User6); header()->setSectionResizeMode(User1, QHeaderView::Interactive); header()->setSectionResizeMode(User2, QHeaderView::Interactive); header()->setSectionResizeMode(User3, QHeaderView::Interactive); header()->setSectionResizeMode(User4, QHeaderView::Interactive); header()->setSectionResizeMode(User5, QHeaderView::Interactive); header()->setSectionResizeMode(User6, QHeaderView::Stretch); connect(this, &DItemsListView::itemClicked, this, &DItemsListView::slotItemClicked); } void DItemsListView::enableDragAndDrop(const bool enable) { setDragEnabled(enable); viewport()->setAcceptDrops(enable); setDragDropMode(enable ? QAbstractItemView::InternalMove : QAbstractItemView::NoDragDrop); setDragDropOverwriteMode(enable); setDropIndicatorShown(enable); } void DItemsListView::drawRow(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const { DItemsListViewItem* const item = dynamic_cast(itemFromIndex(index)); if (item && !item->hasValidThumbnail()) { DItemsList* const view = dynamic_cast(parent()); if (view) { view->updateThumbnail(item->url()); } } QTreeWidget::drawRow(p, opt, index); } void DItemsListView::slotItemClicked(QTreeWidgetItem* item, int column) { Q_UNUSED(column) if (!item) { return; } emit signalItemClicked(item); } void DItemsListView::setColumnLabel(ColumnType column, const QString& label) { headerItem()->setText(column, label); } void DItemsListView::setColumnEnabled(ColumnType column, bool enable) { if (enable) { showColumn(column); } else { hideColumn(column); } } void DItemsListView::setColumn(ColumnType column, const QString& label, bool enable) { setColumnLabel(column, label); setColumnEnabled(column, enable); } DItemsListViewItem* DItemsListView::findItem(const QUrl& url) { QTreeWidgetItemIterator it(this); while (*it) { DItemsListViewItem* const lvItem = dynamic_cast(*it); if (lvItem && lvItem->url() == url) { return lvItem; } ++it; } return nullptr; } QModelIndex DItemsListView::indexFromItem(DItemsListViewItem* item, int column) const { return QTreeWidget::indexFromItem(item, column); } void DItemsListView::contextMenuEvent(QContextMenuEvent* e) { QTreeWidget::contextMenuEvent(e); emit signalContextMenuRequested(); } void DItemsListView::dragEnterEvent(QDragEnterEvent* e) { QTreeWidget::dragEnterEvent(e); if (e->mimeData()->hasUrls()) { e->acceptProposedAction(); } } void DItemsListView::dragMoveEvent(QDragMoveEvent* e) { QTreeWidget::dragMoveEvent(e); if (e->mimeData()->hasUrls()) { e->acceptProposedAction(); } } void DItemsListView::dropEvent(QDropEvent* e) { QTreeWidget::dropEvent(e); QList list = e->mimeData()->urls(); QList urls; - foreach(const QUrl& url, list) + foreach (const QUrl& url, list) { QFileInfo fi(url.toLocalFile()); if (fi.isFile() && fi.exists()) { urls.append(url); } } if (!urls.isEmpty()) { emit signalAddedDropedItems(urls); } } // --------------------------------------------------------------------------- CtrlButton::CtrlButton(const QIcon& icon, QWidget* const parent) : QPushButton(parent) { const int btnSize = 32; setMinimumSize(btnSize, btnSize); setMaximumSize(btnSize, btnSize); setIcon(icon); } CtrlButton::~CtrlButton() { } // --------------------------------------------------------------------------- class Q_DECL_HIDDEN DItemsList::Private { public: explicit Private() { listView = nullptr; addButton = nullptr; removeButton = nullptr; moveUpButton = nullptr; moveDownButton = nullptr; clearButton = nullptr; loadButton = nullptr; saveButton = nullptr; iconSize = DEFAULTSIZE; allowRAW = true; controlButtonsEnabled = true; allowDuplicate = false; progressCount = 0; progressTimer = nullptr; progressPix = DWorkingPixmap(); thumbLoadThread = ThumbnailLoadThread::defaultThread(); iface = nullptr; } bool allowRAW; bool allowDuplicate; bool controlButtonsEnabled; int iconSize; CtrlButton* addButton; CtrlButton* removeButton; CtrlButton* moveUpButton; CtrlButton* moveDownButton; CtrlButton* clearButton; CtrlButton* loadButton; CtrlButton* saveButton; QList processItems; DWorkingPixmap progressPix; int progressCount; QTimer* progressTimer; DItemsListView* listView; ThumbnailLoadThread* thumbLoadThread; DInfoInterface* iface; }; DItemsList::DItemsList(QWidget* const parent, int iconSize) : QWidget(parent), d(new Private) { if (iconSize != -1) // default = ICONSIZE { setIconSize(iconSize); } // -------------------------------------------------------- d->listView = new DItemsListView(d->iconSize, this); d->listView->setSelectionMode(QAbstractItemView::ExtendedSelection); // -------------------------------------------------------- d->addButton = new CtrlButton(QIcon::fromTheme(QLatin1String("list-add")).pixmap(16, 16), this); d->removeButton = new CtrlButton(QIcon::fromTheme(QLatin1String("list-remove")).pixmap(16, 16), this); d->moveUpButton = new CtrlButton(QIcon::fromTheme(QLatin1String("go-up")).pixmap(16, 16), this); d->moveDownButton = new CtrlButton(QIcon::fromTheme(QLatin1String("go-down")).pixmap(16, 16), this); d->clearButton = new CtrlButton(QIcon::fromTheme(QLatin1String("edit-clear")).pixmap(16, 16), this); d->loadButton = new CtrlButton(QIcon::fromTheme(QLatin1String("document-open")).pixmap(16, 16), this); d->saveButton = new CtrlButton(QIcon::fromTheme(QLatin1String("document-save")).pixmap(16, 16), this); d->addButton->setToolTip(i18n("Add new images to the list")); d->removeButton->setToolTip(i18n("Remove selected images from the list")); d->moveUpButton->setToolTip(i18n("Move current selected image up in the list")); d->moveDownButton->setToolTip(i18n("Move current selected image down in the list")); d->clearButton->setToolTip(i18n("Clear the list.")); d->loadButton->setToolTip(i18n("Load a saved list.")); d->saveButton->setToolTip(i18n("Save the list.")); d->progressTimer = new QTimer(this); // -------------------------------------------------------- setControlButtons(Add | Remove | MoveUp | MoveDown | Clear | Save | Load ); // add all buttons (default) setControlButtonsPlacement(ControlButtonsBelow); // buttons on the bottom (default) enableDragAndDrop(true); // enable drag and drop (default) // -------------------------------------------------------- connect(d->listView, &DItemsListView::signalAddedDropedItems, this, &DItemsList::slotAddImages); connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(LoadingDescription,QPixmap)), this, SLOT(slotThumbnail(LoadingDescription,QPixmap))); connect(d->listView, &DItemsListView::signalItemClicked, this, &DItemsList::signalItemClicked); connect(d->listView, &DItemsListView::signalContextMenuRequested, this, &DItemsList::signalContextMenuRequested); // queue this connection because itemSelectionChanged is emitted // while items are deleted, and accessing selectedItems at that // time causes a crash ... connect(d->listView, &DItemsListView::itemSelectionChanged, this, &DItemsList::slotImageListChanged, Qt::QueuedConnection); connect(this, &DItemsList::signalImageListChanged, this, &DItemsList::slotImageListChanged); // -------------------------------------------------------- connect(d->addButton, &CtrlButton::clicked, this, &DItemsList::slotAddItems); connect(d->removeButton, &CtrlButton::clicked, this, &DItemsList::slotRemoveItems); connect(d->moveUpButton, &CtrlButton::clicked, this, &DItemsList::slotMoveUpItems); connect(d->moveDownButton, &CtrlButton::clicked, this, &DItemsList::slotMoveDownItems); connect(d->clearButton, &CtrlButton::clicked, this, &DItemsList::slotClearItems); connect(d->loadButton, &CtrlButton::clicked, this, &DItemsList::slotLoadItems); connect(d->saveButton, &CtrlButton::clicked, this, &DItemsList::slotSaveItems); connect(d->progressTimer, &QTimer::timeout, this, &DItemsList::slotProgressTimerDone); // -------------------------------------------------------- emit signalImageListChanged(); } DItemsList::~DItemsList() { delete d; } void DItemsList::enableControlButtons(bool enable) { d->controlButtonsEnabled = enable; slotImageListChanged(); } void DItemsList::enableDragAndDrop(const bool enable) { d->listView->enableDragAndDrop(enable); } void DItemsList::setControlButtonsPlacement(ControlButtonPlacement placement) { delete layout(); const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QGridLayout* const mainLayout = new QGridLayout; mainLayout->addWidget(d->listView, 1, 1, 1, 1); mainLayout->setRowStretch(1, 10); mainLayout->setColumnStretch(1, 10); mainLayout->setContentsMargins(spacing, spacing, spacing, spacing); mainLayout->setSpacing(spacing); // -------------------------------------------------------- QHBoxLayout* const hBtnLayout = new QHBoxLayout; hBtnLayout->addStretch(10); hBtnLayout->addWidget(d->moveUpButton); hBtnLayout->addWidget(d->moveDownButton); hBtnLayout->addWidget(d->addButton); hBtnLayout->addWidget(d->removeButton); hBtnLayout->addWidget(d->loadButton); hBtnLayout->addWidget(d->saveButton); hBtnLayout->addWidget(d->clearButton); hBtnLayout->addStretch(10); // -------------------------------------------------------- QVBoxLayout* const vBtnLayout = new QVBoxLayout; vBtnLayout->addStretch(10); vBtnLayout->addWidget(d->moveUpButton); vBtnLayout->addWidget(d->moveDownButton); vBtnLayout->addWidget(d->addButton); vBtnLayout->addWidget(d->removeButton); vBtnLayout->addWidget(d->loadButton); vBtnLayout->addWidget(d->saveButton); vBtnLayout->addWidget(d->clearButton); vBtnLayout->addStretch(10); // -------------------------------------------------------- switch (placement) { case ControlButtonsAbove: mainLayout->addLayout(hBtnLayout, 0, 1, 1, 1); delete vBtnLayout; break; case ControlButtonsBelow: mainLayout->addLayout(hBtnLayout, 2, 1, 1, 1); delete vBtnLayout; break; case ControlButtonsLeft: mainLayout->addLayout(vBtnLayout, 1, 0, 1, 1); delete hBtnLayout; break; case ControlButtonsRight: mainLayout->addLayout(vBtnLayout, 1, 2, 1, 1); delete hBtnLayout; break; case NoControlButtons: default: { delete vBtnLayout; delete hBtnLayout; // set all buttons invisible setControlButtons(nullptr); break; } } setLayout(mainLayout); } void DItemsList::setControlButtons(ControlButtons buttonMask) { d->addButton->setVisible(buttonMask & Add); d->removeButton->setVisible(buttonMask & Remove); d->moveUpButton->setVisible(buttonMask & MoveUp); d->moveDownButton->setVisible(buttonMask & MoveDown); d->clearButton->setVisible(buttonMask & Clear); d->loadButton->setVisible(buttonMask & Load); d->saveButton->setVisible(buttonMask & Save); } void DItemsList::setIface(DInfoInterface* const iface) { d->iface = iface; } DInfoInterface* DItemsList::iface() const { return d->iface; } void DItemsList::setAllowDuplicate(bool allow) { d->allowDuplicate = allow; } void DItemsList::setAllowRAW(bool allow) { d->allowRAW = allow; } void DItemsList::setIconSize(int size) { if (size < 16) { d->iconSize = 16; } else if (size > 128) { d->iconSize = 128; } else { d->iconSize = size; } } int DItemsList::iconSize() const { return d->iconSize; } void DItemsList::loadImagesFromCurrentSelection() { bool selection = checkSelection(); if (selection == true) { if (!d->iface) { return; } QList images = d->iface->currentSelectedItems(); if (!images.isEmpty()) { slotAddImages(images); } } else { loadImagesFromCurrentAlbum(); } } void DItemsList::loadImagesFromCurrentAlbum() { if (!d->iface) { return; } QList images = d->iface->currentAlbumItems(); if (!images.isEmpty()) { slotAddImages(images); } } bool DItemsList::checkSelection() { if (!d->iface) { return false; } QList images = d->iface->currentSelectedItems(); return (!images.isEmpty()); } void DItemsList::slotAddImages(const QList& list) { if (list.count() == 0) { return; } QList urls; bool raw = false; for (QList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { QUrl imageUrl = *it; // Check if the new item already exist in the list. bool found = false; QTreeWidgetItemIterator iter(d->listView); while (*iter) { DItemsListViewItem* const item = dynamic_cast(*iter); if (item && item->url() == imageUrl) { found = true; } ++iter; } if (d->allowDuplicate || !found) { // if RAW files are not allowed, skip the image if (!d->allowRAW && DRawDecoder::isRawFile(imageUrl)) { raw = true; continue; } new DItemsListViewItem(listView(), imageUrl); urls.append(imageUrl); } } emit signalAddItems(urls); emit signalImageListChanged(); emit signalFoundRAWImages(raw); } void DItemsList::slotAddItems() { KConfig config; KConfigGroup grp = config.group(objectName()); QUrl lastFileUrl = QUrl::fromLocalFile(grp.readEntry("Last Image Path", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation))); ImageDialog dlg(this, lastFileUrl, false); QList urls = dlg.urls(); if (!urls.isEmpty()) { slotAddImages(urls); grp.writeEntry("Last Image Path", urls.first().adjusted(QUrl::RemoveFilename).toLocalFile()); config.sync(); } } void DItemsList::slotRemoveItems() { QList selectedItemsList = d->listView->selectedItems(); QList itemsIndex; for (QList::const_iterator it = selectedItemsList.constBegin(); it != selectedItemsList.constEnd(); ++it) { DItemsListViewItem* const item = dynamic_cast(*it); if (item) { itemsIndex.append(d->listView->indexFromItem(item).row()); if (d->processItems.contains(item->url())) { d->processItems.removeAll(item->url()); } d->listView->removeItemWidget(*it, 0); delete *it; } } emit signalRemovedItems(itemsIndex); emit signalImageListChanged(); } void DItemsList::slotMoveUpItems() { // move above item down, then we don't have to fix the focus QModelIndex curIndex = listView()->currentIndex(); if (!curIndex.isValid()) { return; } QModelIndex aboveIndex = listView()->indexAbove(curIndex); if (!aboveIndex.isValid()) { return; } QTreeWidgetItem* const temp = listView()->takeTopLevelItem(aboveIndex.row()); listView()->insertTopLevelItem(curIndex.row(), temp); // this is a quick fix. We loose the extra tags in flickr upload, but at list we don't get a crash DItemsListViewItem* const uw = dynamic_cast(temp); if (uw) uw->updateItemWidgets(); emit signalImageListChanged(); emit signalMoveUpItem(); } void DItemsList::slotMoveDownItems() { // move below item up, then we don't have to fix the focus QModelIndex curIndex = listView()->currentIndex(); if (!curIndex.isValid()) { return; } QModelIndex belowIndex = listView()->indexBelow(curIndex); if (!belowIndex.isValid()) { return; } QTreeWidgetItem* const temp = listView()->takeTopLevelItem(belowIndex.row()); listView()->insertTopLevelItem(curIndex.row(), temp); // This is a quick fix. We can loose extra tags in uploader, but at least we don't get a crash DItemsListViewItem* const uw = dynamic_cast(temp); if (uw) uw->updateItemWidgets(); emit signalImageListChanged(); emit signalMoveDownItem(); } void DItemsList::slotClearItems() { listView()->selectAll(); slotRemoveItems(); listView()->clear(); } void DItemsList::slotLoadItems() { KConfig config; KConfigGroup grp = config.group(objectName()); QUrl lastFileUrl = QUrl::fromLocalFile(grp.readEntry("Last Images List Path", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation))); QUrl loadLevelsFile; loadLevelsFile = DFileDialog::getOpenFileUrl(this, i18n("Select the image file list to load"), lastFileUrl, i18n("All Files (*)")); if (loadLevelsFile.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "empty url"; return; } QFile file(loadLevelsFile.toLocalFile()); qCDebug(DIGIKAM_GENERAL_LOG) << "file path " << loadLevelsFile.toLocalFile(); if (!file.open(QIODevice::ReadOnly)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open file"; return; } QXmlStreamReader xmlReader; xmlReader.setDevice(&file); while (!xmlReader.atEnd()) { if (xmlReader.isStartElement() && xmlReader.name() == QLatin1String("Image")) { // get all attributes and its value of a tag in attrs variable. QXmlStreamAttributes attrs = xmlReader.attributes(); // get value of each attribute from QXmlStreamAttributes QStringRef url = attrs.value(QLatin1String("url")); if (url.isEmpty()) { xmlReader.readNext(); continue; } QList urls; urls.append(QUrl(url.toString())); if (!urls.isEmpty()) { //allow tools to append a new file slotAddImages(urls); // read tool Image custom attributes and children element emit signalXMLLoadImageElement(xmlReader); } } else if (xmlReader.isStartElement() && xmlReader.name() != QLatin1String("Images")) { // unmanaged start element (it should be tools one) emit signalXMLCustomElements(xmlReader); } else if (xmlReader.isEndElement() && xmlReader.name() == QLatin1String("Images")) { // if EndElement is Images return grp.writeEntry("Last Images List Path", loadLevelsFile.adjusted(QUrl::RemoveFilename).toLocalFile()); config.sync(); file.close(); return; } xmlReader.readNext(); } file.close(); } void DItemsList::slotSaveItems() { KConfig config; KConfigGroup grp = config.group(objectName()); QUrl lastFileUrl = QUrl::fromLocalFile(grp.readEntry("Last Images List Path", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation))); QUrl saveLevelsFile; saveLevelsFile = DFileDialog::getSaveFileUrl(this, i18n("Select the image file list to save"), lastFileUrl, i18n("All Files (*)")); qCDebug(DIGIKAM_GENERAL_LOG) << "file url " << saveLevelsFile.toDisplayString(); if (saveLevelsFile.isEmpty()) { qCDebug(DIGIKAM_GENERAL_LOG) << "empty url"; return; } QFile file(saveLevelsFile.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open target file"; return; } QXmlStreamWriter xmlWriter; xmlWriter.setDevice(&file); xmlWriter.setAutoFormatting(true); xmlWriter.writeStartDocument(); xmlWriter.writeStartElement(QLatin1String("Images")); QTreeWidgetItemIterator it(listView()); while (*it) { DItemsListViewItem* const lvItem = dynamic_cast(*it); if (lvItem) { xmlWriter.writeStartElement(QLatin1String("Image")); xmlWriter.writeAttribute(QLatin1String("url"), lvItem->url().toDisplayString()); emit signalXMLSaveItem(xmlWriter, listView()->indexFromItem(lvItem).row()); xmlWriter.writeEndElement(); // Image } ++it; } emit signalXMLCustomElements(xmlWriter); xmlWriter.writeEndElement(); // Images xmlWriter.writeEndDocument(); // end document grp.writeEntry("Last Images List Path", saveLevelsFile.adjusted(QUrl::RemoveFilename).toLocalFile()); config.sync(); file.close(); } void DItemsList::removeItemByUrl(const QUrl& url) { bool found; QList itemsIndex; do { found = false; QTreeWidgetItemIterator it(d->listView); while (*it) { DItemsListViewItem* const item = dynamic_cast(*it); if (item && item->url() == url) { itemsIndex.append(d->listView->indexFromItem(item).row()); if (d->processItems.contains(item->url())) { d->processItems.removeAll(item->url()); } delete item; found = true; break; } ++it; } } while (found); emit signalRemovedItems(itemsIndex); emit signalImageListChanged(); } QList DItemsList::imageUrls(bool onlyUnprocessed) const { QList list; QTreeWidgetItemIterator it(d->listView); while (*it) { DItemsListViewItem* const item = dynamic_cast(*it); if (item) { if ((onlyUnprocessed == false) || (item->state() != DItemsListViewItem::Success)) { list.append(item->url()); } } ++it; } return list; } void DItemsList::slotProgressTimerDone() { if (!d->processItems.isEmpty()) { - foreach(const QUrl& url, d->processItems) + foreach (const QUrl& url, d->processItems) { DItemsListViewItem* const item = listView()->findItem(url); if (item) item->setProgressAnimation(d->progressPix.frameAt(d->progressCount)); } d->progressCount++; if (d->progressCount == 8) { d->progressCount = 0; } d->progressTimer->start(300); } } void DItemsList::processing(const QUrl& url) { DItemsListViewItem* const item = listView()->findItem(url); if (item) { d->processItems.append(url); d->listView->setCurrentItem(item, true); d->listView->scrollToItem(item); d->progressTimer->start(300); } } void DItemsList::processed(const QUrl& url, bool success) { DItemsListViewItem* const item = listView()->findItem(url); if (item) { d->processItems.removeAll(url); item->setProcessedIcon(QIcon::fromTheme(success ? QLatin1String("dialog-ok-apply") : QLatin1String("dialog-cancel")).pixmap(16, 16)); item->setState(success ? DItemsListViewItem::Success : DItemsListViewItem::Failed); if (d->processItems.isEmpty()) d->progressTimer->stop(); } } void DItemsList::cancelProcess() { - foreach(const QUrl& url, d->processItems) + foreach (const QUrl& url, d->processItems) { processed(url, false); } } void DItemsList::clearProcessedStatus() { QTreeWidgetItemIterator it(d->listView); while (*it) { DItemsListViewItem* const lvItem = dynamic_cast(*it); if (lvItem) { lvItem->setProcessedIcon(QIcon()); } ++it; } } DItemsListView* DItemsList::listView() const { return d->listView; } void DItemsList::slotImageListChanged() { const QList selectedItemsList = d->listView->selectedItems(); const bool haveImages = !(imageUrls().isEmpty()) && d->controlButtonsEnabled; const bool haveSelectedImages = !(selectedItemsList.isEmpty()) && d->controlButtonsEnabled; const bool haveOnlyOneSelectedImage = (selectedItemsList.count() == 1) && d->controlButtonsEnabled; d->removeButton->setEnabled(haveSelectedImages); d->moveUpButton->setEnabled(haveOnlyOneSelectedImage); d->moveDownButton->setEnabled(haveOnlyOneSelectedImage); d->clearButton->setEnabled(haveImages); // All buttons are enabled / disabled now, but the "Add" button should always be // enabled, if the buttons are not explicitly disabled with enableControlButtons() d->addButton->setEnabled(d->controlButtonsEnabled); // TODO: should they be enabled by default now? d->loadButton->setEnabled(d->controlButtonsEnabled); d->saveButton->setEnabled(d->controlButtonsEnabled); } void DItemsList::updateThumbnail(const QUrl& url) { d->thumbLoadThread->find(ThumbnailIdentifier(url.toLocalFile())); } void DItemsList::slotThumbnail(const LoadingDescription& desc, const QPixmap& pix) { QTreeWidgetItemIterator it(d->listView); while (*it) { DItemsListViewItem* const item = dynamic_cast(*it); if (item && item->url() == QUrl::fromLocalFile(desc.filePath)) { if (!pix.isNull()) { item->setThumb(pix.scaled(d->iconSize, d->iconSize, Qt::KeepAspectRatio)); } if (!d->allowDuplicate) { return; } } ++it; } } DItemsListViewItem* DItemsListView::getCurrentItem() const { QTreeWidgetItem* const currentTreeItem = currentItem(); if (!currentTreeItem) { return nullptr; } return dynamic_cast(currentTreeItem); } QUrl DItemsList::getCurrentUrl() const { DItemsListViewItem* const currentItem = d->listView->getCurrentItem(); if (!currentItem) { return QUrl(); } return currentItem->url(); } } // namespace Digikam diff --git a/core/libs/filters/colorlabelfilter.cpp b/core/libs/filters/colorlabelfilter.cpp index cc48392226..4b94e7d77f 100644 --- a/core/libs/filters/colorlabelfilter.cpp +++ b/core/libs/filters/colorlabelfilter.cpp @@ -1,81 +1,81 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-02-09 * Description : color label filter * * Copyright (C) 2011-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "colorlabelfilter.h" // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "tagscache.h" namespace Digikam { ColorLabelFilter::ColorLabelFilter(QWidget* const parent) : ColorLabelWidget(parent) { setDescriptionBoxVisible(false); setButtonsExclusive(false); reset(); connect(this, SIGNAL(signalColorLabelChanged(int)), this, SLOT(slotColorLabelSelectionChanged())); } ColorLabelFilter::~ColorLabelFilter() { } void ColorLabelFilter::reset() { setColorLabels(QList()); slotColorLabelSelectionChanged(); } QList ColorLabelFilter::getCheckedColorLabelTags() { QList list; int tagId = 0; TAlbum* tag = nullptr; - foreach(const ColorLabel& cl, colorLabels()) + foreach (const ColorLabel& cl, colorLabels()) { tagId = TagsCache::instance()->tagForColorLabel(cl); tag = AlbumManager::instance()->findTAlbum(tagId); if (tagId) { list.append(tag); } } return list; } void ColorLabelFilter::slotColorLabelSelectionChanged() { emit signalColorLabelSelectionChanged(colorLabels()); } } // namespace Digikam diff --git a/core/libs/filters/picklabelfilter.cpp b/core/libs/filters/picklabelfilter.cpp index a0f426a4f5..f2a7efbb7a 100644 --- a/core/libs/filters/picklabelfilter.cpp +++ b/core/libs/filters/picklabelfilter.cpp @@ -1,80 +1,80 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-02-16 * Description : pick label filter * * Copyright (C) 2011-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "picklabelfilter.h" // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "tagscache.h" namespace Digikam { PickLabelFilter::PickLabelFilter(QWidget* const parent) : PickLabelWidget(parent) { setDescriptionBoxVisible(false); setButtonsExclusive(false); reset(); connect(this, SIGNAL(signalPickLabelChanged(int)), this, SLOT(slotPickLabelSelectionChanged())); } PickLabelFilter::~PickLabelFilter() { } void PickLabelFilter::reset() { setPickLabels(QList()); slotPickLabelSelectionChanged(); } QList PickLabelFilter::getCheckedPickLabelTags() { QList list; int tagId = 0; TAlbum* tag = nullptr; - foreach(const PickLabel& pl, colorLabels()) + foreach (const PickLabel& pl, colorLabels()) { tagId = TagsCache::instance()->tagForPickLabel(pl); tag = AlbumManager::instance()->findTAlbum(tagId); if (tagId) { list.append(tag); } } return list; } void PickLabelFilter::slotPickLabelSelectionChanged() { emit signalPickLabelSelectionChanged(colorLabels()); } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp b/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp index 5e6e14a35a..9aa72d7e6c 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_exif.cpp @@ -1,175 +1,175 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - Exif helpers. * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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, 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. * * ============================================================ */ #include "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "iccprofile.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { int DMetadata::getMSecsInfo() const { int ms = 0; bool ok = mSecTimeStamp("Exif.Photo.SubSecTime", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeOriginal", ms); if (ok) return ms; ok = mSecTimeStamp("Exif.Photo.SubSecTimeDigitized", ms); if (ok) return ms; return 0; } bool DMetadata::mSecTimeStamp(const char* const exifTagName, int& ms) const { bool ok = false; QString val = getExifTagString(exifTagName); if (!val.isEmpty()) { int sub = val.toUInt(&ok); if (ok) { int _ms = (int)(QString::fromLatin1("0.%1").arg(sub).toFloat(&ok) * 1000.0); if (ok) { ms = _ms; qCDebug(DIGIKAM_METAENGINE_LOG) << "msec timestamp: " << ms; } } } return ok; } IccProfile DMetadata::getIccProfile() const { // Check if Exif data contains an ICC color profile. QByteArray data = getExifTagData("Exif.Image.InterColorProfile"); if (!data.isNull()) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Found an ICC profile in Exif metadata"; return IccProfile(data); } // Else check the Exif color-space tag and use default profiles that we ship switch (getItemColorWorkSpace()) { case DMetadata::WORKSPACE_SRGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is sRGB. Using default sRGB ICC profile."; return IccProfile::sRGB(); } case DMetadata::WORKSPACE_ADOBERGB: { qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile."; return IccProfile::adobeRGB(); } default: break; } return IccProfile(); } bool DMetadata::setIccProfile(const IccProfile& profile) { if (profile.isNull()) { removeExifTag("Exif.Image.InterColorProfile"); } else { QByteArray data = IccProfile(profile).data(); if (!setExifTagData("Exif.Image.InterColorProfile", data)) { return false; } } removeExifColorSpace(); return true; } bool DMetadata::removeExifColorSpace() const { bool ret = true; ret &= removeExifTag("Exif.Photo.ColorSpace"); ret &= removeXmpTag("Xmp.exif.ColorSpace"); return ret; } QString DMetadata::getExifTagStringFromTagsList(const QStringList& tagsList) const { QString val; - foreach(const QString& tag, tagsList) + foreach (const QString& tag, tagsList) { val = getExifTagString(tag.toLatin1().constData()); if (!val.isEmpty()) return val; } return QString(); } bool DMetadata::removeExifTags(const QStringList& tagFilters) { MetaDataMap m = getExifTagsDataList(tagFilters); if (m.isEmpty()) return false; for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it) { removeExifTag(it.key().toLatin1().constData()); } return true; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp b/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp index 545da6b08c..e32877bea6 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_generic.cpp @@ -1,917 +1,917 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - generic helpers * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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, 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. * * ============================================================ */ #include "dmetadata.h" // Qt includes #include // KDE includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { QVariant DMetadata::fromExifOrXmp(const char* const exifTagName, const char* const xmpTagName) const { QVariant var; if (exifTagName) { var = getExifTagVariant(exifTagName, false); if (!var.isNull()) { return var; } } if (xmpTagName) { var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return var; } QVariant DMetadata::fromIptcOrXmp(const char* const iptcTagName, const char* const xmpTagName) const { if (iptcTagName) { QString iptcValue = getIptcTagString(iptcTagName); if (!iptcValue.isNull()) { return iptcValue; } } if (xmpTagName) { QVariant var = getXmpTagVariant(xmpTagName); if (!var.isNull()) { return var; } } return QVariant(QVariant::String); } QVariant DMetadata::getMetadataField(MetadataInfo::Field field) const { switch (field) { case MetadataInfo::Comment: { return getItemComments()[QLatin1String("x-default")].caption; } case MetadataInfo::CommentJfif: { return getCommentsDecoded(); } case MetadataInfo::CommentExif: { return getExifComment(); } case MetadataInfo::CommentIptc: { return fromIptcOrXmp("Iptc.Application2.Caption", nullptr); } case MetadataInfo::Description: { QVariant var = fromXmpLangAlt("Xmp.dc.description"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.ImageDescription"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Caption"); } case MetadataInfo::Headline: { return fromIptcOrXmp("Iptc.Application2.Headline", "Xmp.photoshop.Headline"); } case MetadataInfo::Title: { QString str = getItemTitles()[QLatin1String("x-default")].caption; if (str.isEmpty()) { return QVariant(QVariant::Map); } QMap map; map[QLatin1String("x-default")] = str; return map; } case MetadataInfo::DescriptionWriter: { return fromIptcOrXmp("Iptc.Application2.Writer", "Xmp.photoshop.CaptionWriter"); } case MetadataInfo::Keywords: { QStringList list; getItemTagsPath(list); return toStringListVariant(list); } case MetadataInfo::Faces: { QMultiMap faceMap; getItemFacesMap(faceMap); QVariant var(faceMap); return var; } case MetadataInfo::Rating: { return getItemRating(); } case MetadataInfo::CreationDate: { return getItemDateTime(); } case MetadataInfo::DigitizationDate: { return getDigitizationDateTime(true); } case MetadataInfo::Orientation: { return (int)getItemOrientation(); } case MetadataInfo::Make: { QVariant var = fromExifOrXmp("Exif.Image.Make", "Xmp.tiff.Make"); return QVariant(var.toString().trimmed()); } case MetadataInfo::Model: { QVariant var = fromExifOrXmp("Exif.Image.Model", "Xmp.tiff.Model"); return QVariant(var.toString().trimmed()); } case MetadataInfo::Lens: { return getLensDescription(); } case MetadataInfo::Aperture: { QVariant var = fromExifOrXmp("Exif.Photo.FNumber", "Xmp.exif.FNumber"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ApertureValue", "Xmp.exif.ApertureValue"); if (!var.isNull()) { var = apexApertureToFNumber(var.toDouble()); } } return var; } case MetadataInfo::FocalLength: { return fromExifOrXmp("Exif.Photo.FocalLength", "Xmp.exif.FocalLength"); } case MetadataInfo::FocalLengthIn35mm: { return fromExifOrXmp("Exif.Photo.FocalLengthIn35mmFilm", "Xmp.exif.FocalLengthIn35mmFilm"); } case MetadataInfo::ExposureTime: { QVariant var = fromExifOrXmp("Exif.Photo.ExposureTime", "Xmp.exif.ExposureTime"); if (var.isNull()) { var = fromExifOrXmp("Exif.Photo.ShutterSpeedValue", "Xmp.exif.ShutterSpeedValue"); if (!var.isNull()) { var = apexShutterSpeedToExposureTime(var.toDouble()); } } return var; } case MetadataInfo::ExposureProgram: { return fromExifOrXmp("Exif.Photo.ExposureProgram", "Xmp.exif.ExposureProgram"); } case MetadataInfo::ExposureMode: { return fromExifOrXmp("Exif.Photo.ExposureMode", "Xmp.exif.ExposureMode"); } case MetadataInfo::Sensitivity: { QVariant var = fromExifOrXmp("Exif.Photo.ISOSpeedRatings", "Xmp.exif.ISOSpeedRatings"); //if (var.isNull()) // TODO: has this ISO format??? We must convert to the format of ISOSpeedRatings! // var = fromExifOrXmp("Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex"); return var; } case MetadataInfo::FlashMode: { return fromExifOrXmp("Exif.Photo.Flash", "Xmp.exif.Flash"); } case MetadataInfo::WhiteBalance: { return fromExifOrXmp("Exif.Photo.WhiteBalance", "Xmp.exif.WhiteBalance"); } case MetadataInfo::MeteringMode: { return fromExifOrXmp("Exif.Photo.MeteringMode", "Xmp.exif.MeteringMode"); } case MetadataInfo::SubjectDistance: { return fromExifOrXmp("Exif.Photo.SubjectDistance", "Xmp.exif.SubjectDistance"); } case MetadataInfo::SubjectDistanceCategory: { return fromExifOrXmp("Exif.Photo.SubjectDistanceRange", "Xmp.exif.SubjectDistanceRange"); } case MetadataInfo::WhiteBalanceColorTemperature: { //TODO: ?? return QVariant(QVariant::Int); } case MetadataInfo::Longitude: { return getGPSLongitudeString(); } case MetadataInfo::LongitudeNumber: { double longitude; if (getGPSLongitudeNumber(&longitude)) { return longitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::Latitude: { return getGPSLatitudeString(); } case MetadataInfo::LatitudeNumber: { double latitude; if (getGPSLatitudeNumber(&latitude)) { return latitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::Altitude: { double altitude; if (getGPSAltitude(&altitude)) { return altitude; } else { return QVariant(QVariant::Double); } } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: // TODO or unsupported? return QVariant(QVariant::Double); case MetadataInfo::PositionDescription: // TODO or unsupported? return QVariant(QVariant::String); case MetadataInfo::IptcCoreCopyrightNotice: { QVariant var = fromXmpLangAlt("Xmp.dc.rights"); if (!var.isNull()) { return var; } var = fromXmpLangAlt("Xmp.tiff.Copyright"); if (!var.isNull()) { return var; } return fromIptcEmulateLangAlt("Iptc.Application2.Copyright"); } case MetadataInfo::IptcCoreCreator: { QVariant var = fromXmpList("Xmp.dc.creator"); if (!var.isNull()) { return var; } QString artist = getXmpTagString("Xmp.tiff.Artist"); if (!artist.isNull()) { QStringList list; list << artist; return list; } return fromIptcEmulateList("Iptc.Application2.Byline"); } case MetadataInfo::IptcCoreProvider: return fromIptcOrXmp("Iptc.Application2.Credit", "Xmp.photoshop.Credit"); case MetadataInfo::IptcCoreRightsUsageTerms: return fromXmpLangAlt("Xmp.xmpRights.UsageTerms"); case MetadataInfo::IptcCoreSource: return fromIptcOrXmp("Iptc.Application2.Source", "Xmp.photoshop.Source"); case MetadataInfo::IptcCoreCreatorJobTitle: return fromIptcOrXmp("Iptc.Application2.BylineTitle", "Xmp.photoshop.AuthorsPosition"); case MetadataInfo::IptcCoreInstructions: return fromIptcOrXmp("Iptc.Application2.SpecialInstructions", "Xmp.photoshop.Instructions"); case MetadataInfo::IptcCoreLocationInfo: { IptcCoreLocationInfo location = getIptcCoreLocation(); if (location.isNull()) { return QVariant(); } return QVariant::fromValue(location); } case MetadataInfo::IptcCoreCountryCode: return fromIptcOrXmp("Iptc.Application2.CountryCode", "Xmp.iptc.CountryCode"); case MetadataInfo::IptcCoreCountry: return fromIptcOrXmp("Iptc.Application2.CountryName", "Xmp.photoshop.Country"); case MetadataInfo::IptcCoreCity: return fromIptcOrXmp("Iptc.Application2.City", "Xmp.photoshop.City"); case MetadataInfo::IptcCoreLocation: return fromIptcOrXmp("Iptc.Application2.SubLocation", "Xmp.iptc.Location"); case MetadataInfo::IptcCoreProvinceState: return fromIptcOrXmp("Iptc.Application2.ProvinceState", "Xmp.photoshop.State"); case MetadataInfo::IptcCoreIntellectualGenre: return fromIptcOrXmp("Iptc.Application2.ObjectAttribute", "Xmp.iptc.IntellectualGenre"); case MetadataInfo::IptcCoreJobID: return fromIptcOrXmp("Iptc.Application2.TransmissionReference", "Xmp.photoshop.TransmissionReference"); case MetadataInfo::IptcCoreScene: return fromXmpList("Xmp.iptc.Scene"); case MetadataInfo::IptcCoreSubjectCode: return toStringListVariant(getIptcCoreSubjects()); case MetadataInfo::IptcCoreContactInfo: { IptcCoreContactInfo info = getCreatorContactInfo(); if (info.isNull()) { return QVariant(); } return QVariant::fromValue(info); } case MetadataInfo::IptcCoreContactInfoCity: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity"); case MetadataInfo::IptcCoreContactInfoCountry: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry"); case MetadataInfo::IptcCoreContactInfoAddress: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr"); case MetadataInfo::IptcCoreContactInfoPostalCode: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode"); case MetadataInfo::IptcCoreContactInfoProvinceState: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion"); case MetadataInfo::IptcCoreContactInfoEmail: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork"); case MetadataInfo::IptcCoreContactInfoPhone: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork"); case MetadataInfo::IptcCoreContactInfoWebUrl: return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork"); case MetadataInfo::AspectRatio: { long num = 0; long den = 1; // NOTE: there is a bug in Exiv2 xmp::video tag definition as "Rational" value is defined as "Ratio"... //QList list = getXmpTagVariant("Xmp.video.AspectRatio").toList(); QString ar = getXmpTagString("Xmp.video.AspectRatio"); QStringList list = ar.split(QLatin1Char('/')); if (list.size() >= 1) num = list[0].toInt(); if (list.size() >= 2) den = list[1].toInt(); return QString::number((double)num / (double)den); } case MetadataInfo::AudioBitRate: return fromXmpLangAlt("Xmp.audio.SampleRate"); case MetadataInfo::AudioChannelType: return fromXmpLangAlt("Xmp.audio.ChannelType"); case MetadataInfo::AudioCodec: return fromXmpLangAlt("Xmp.audio.Codec"); case MetadataInfo::Duration: return fromXmpLangAlt("Xmp.video.duration"); // duration is in ms case MetadataInfo::FrameRate: return fromXmpLangAlt("Xmp.video.FrameRate"); case MetadataInfo::VideoCodec: return fromXmpLangAlt("Xmp.video.Codec"); case MetadataInfo::VideoBitDepth: return fromXmpLangAlt("Xmp.video.BitDepth"); case MetadataInfo::VideoHeight: return fromXmpLangAlt("Xmp.video.Height"); case MetadataInfo::VideoWidth: return fromXmpLangAlt("Xmp.video.Width"); case MetadataInfo::VideoColorSpace: { QString cs = getXmpTagString("Xmp.video.ColorSpace"); if (cs == QLatin1String("sRGB")) return QString::number(VIDEOCOLORMODEL_SRGB); else if (cs == QLatin1String("CCIR-601")) return QString::number(VIDEOCOLORMODEL_BT601); else if (cs == QLatin1String("CCIR-709")) return QString::number(VIDEOCOLORMODEL_BT709); else if (cs == QLatin1String("Other")) return QString::number(VIDEOCOLORMODEL_OTHER); else return QVariant(QVariant::Int); } default: return QVariant(); } } QVariantList DMetadata::getMetadataFields(const MetadataFields& fields) const { QVariantList list; - foreach(MetadataInfo::Field field, fields) // krazy:exclude=foreach + foreach (MetadataInfo::Field field, fields) // krazy:exclude=foreach { list << getMetadataField(field); } return list; } QString DMetadata::valueToString(const QVariant& value, MetadataInfo::Field field) { MetaEngine exiv2Iface; switch (field) { case MetadataInfo::Rating: return value.toString(); case MetadataInfo::CreationDate: case MetadataInfo::DigitizationDate: return value.toDateTime().toString(Qt::LocaleDate); case MetadataInfo::Orientation: { switch (value.toInt()) { // Example why the English text differs from the enum names: ORIENTATION_ROT_90. // Rotation by 90 degrees is right (clockwise) rotation. // But: The enum names describe what needs to be done to get the image right again. // And an image that needs to be rotated 90 degrees is currently rotated 270 degrees = left. case ORIENTATION_UNSPECIFIED: return i18n("Unspecified"); case ORIENTATION_NORMAL: return i18nc("Rotation of an unrotated image", "Normal"); case ORIENTATION_HFLIP: return i18n("Flipped Horizontally"); case ORIENTATION_ROT_180: return i18n("Rotated by 180 Degrees"); case ORIENTATION_VFLIP: return i18n("Flipped Vertically"); case ORIENTATION_ROT_90_HFLIP: return i18n("Flipped Horizontally and Rotated Left"); case ORIENTATION_ROT_90: return i18n("Rotated Left"); case ORIENTATION_ROT_90_VFLIP: return i18n("Flipped Vertically and Rotated Left"); case ORIENTATION_ROT_270: return i18n("Rotated Right"); default: return i18n("Unknown"); } break; } case MetadataInfo::Make: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Make", value); case MetadataInfo::Model: return exiv2Iface.createExifUserStringFromValue("Exif.Image.Model", value); case MetadataInfo::Lens: // heterogeneous source, non-standardized string return value.toString(); case MetadataInfo::Aperture: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FNumber", value); case MetadataInfo::FocalLength: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLength", value); case MetadataInfo::FocalLengthIn35mm: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLengthIn35mmFilm", value); case MetadataInfo::ExposureTime: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureTime", value); case MetadataInfo::ExposureProgram: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureProgram", value); case MetadataInfo::ExposureMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureMode", value); case MetadataInfo::Sensitivity: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ISOSpeedRatings", value); case MetadataInfo::FlashMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.Flash", value); case MetadataInfo::WhiteBalance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.WhiteBalance", value); case MetadataInfo::MeteringMode: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.MeteringMode", value); case MetadataInfo::SubjectDistance: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistance", value); case MetadataInfo::SubjectDistanceCategory: return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistanceRange", value); case MetadataInfo::WhiteBalanceColorTemperature: return i18nc("Temperature in Kelvin", "%1 K", value.toInt()); case MetadataInfo::AspectRatio: case MetadataInfo::AudioBitRate: case MetadataInfo::AudioChannelType: case MetadataInfo::AudioCodec: case MetadataInfo::Duration: case MetadataInfo::FrameRate: case MetadataInfo::VideoCodec: return value.toString(); case MetadataInfo::Longitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::LongitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ? i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::Latitude: { int degrees, minutes; double seconds; char directionRef; if (!convertToUserPresentableNumbers(value.toString(), °rees, &minutes, &seconds, &directionRef)) { return QString(); } QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::LatitudeNumber: { int degrees, minutes; double seconds; char directionRef; convertToUserPresentableNumbers(false, value.toDouble(), °rees, &minutes, &seconds, &directionRef); QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ? i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South"); return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0)) .arg(minutes).arg(QChar(0x2032)) .arg(seconds, 'f').arg(QChar(0x2033)).arg(direction); } case MetadataInfo::Altitude: { QString meters = QString::fromLatin1("%L1").arg(value.toDouble(), 0, 'f', 2); // xgettext: no-c-format return i18nc("Height in meters", "%1m", meters); } case MetadataInfo::PositionOrientation: case MetadataInfo::PositionTilt: case MetadataInfo::PositionRoll: case MetadataInfo::PositionAccuracy: //TODO return value.toString(); case MetadataInfo::PositionDescription: return value.toString(); // Lang Alt case MetadataInfo::IptcCoreCopyrightNotice: case MetadataInfo::IptcCoreRightsUsageTerms: case MetadataInfo::Description: case MetadataInfo::Title: { QMap map = value.toMap(); // the most common cases if (map.isEmpty()) { return QString(); } else if (map.size() == 1) { return map.begin().value().toString(); } // Try "en-us" QString spec = QLocale().name().toLower().replace(QLatin1Char('_'), QLatin1Char('-')); if (map.contains(spec)) { return map[spec].toString(); } // Try "en-" QStringList keys = map.keys(); QString spec2 = QLocale().name().toLower(); QRegExp exp(spec2.left(spec2.indexOf(QLatin1Char('_'))) + QLatin1Char('-')); QStringList matches = keys.filter(exp); if (!matches.isEmpty()) { return map[matches.first()].toString(); } // return default if (map.contains(QLatin1String("x-default"))) { return map[QLatin1String("x-default")].toString(); } // return first entry return map.begin().value().toString(); } // List case MetadataInfo::IptcCoreCreator: case MetadataInfo::IptcCoreScene: case MetadataInfo::IptcCoreSubjectCode: return value.toStringList().join(QLatin1Char(' ')); // Text case MetadataInfo::Comment: case MetadataInfo::CommentJfif: case MetadataInfo::CommentExif: case MetadataInfo::CommentIptc: case MetadataInfo::Headline: case MetadataInfo::DescriptionWriter: case MetadataInfo::IptcCoreProvider: case MetadataInfo::IptcCoreSource: case MetadataInfo::IptcCoreCreatorJobTitle: case MetadataInfo::IptcCoreInstructions: case MetadataInfo::IptcCoreCountryCode: case MetadataInfo::IptcCoreCountry: case MetadataInfo::IptcCoreCity: case MetadataInfo::IptcCoreLocation: case MetadataInfo::IptcCoreProvinceState: case MetadataInfo::IptcCoreIntellectualGenre: case MetadataInfo::IptcCoreJobID: return value.toString(); default: break; } return QString(); } QStringList DMetadata::valuesToString(const QVariantList& values, const MetadataFields& fields) { int size = values.size(); Q_ASSERT(size == values.size()); QStringList list; for (int i = 0 ; i < size ; ++i) { list << valueToString(values.at(i), fields.at(i)); } return list; } QMap DMetadata::possibleValuesForEnumField(MetadataInfo::Field field) { QMap map; int min, max; switch (field) { case MetadataInfo::Orientation: /// Int, enum from libMetaEngine min = ORIENTATION_UNSPECIFIED; max = ORIENTATION_ROT_270; break; case MetadataInfo::ExposureProgram: /// Int, enum from Exif min = 0; max = 8; break; case MetadataInfo::ExposureMode: /// Int, enum from Exif min = 0; max = 2; break; case MetadataInfo::WhiteBalance: /// Int, enum from Exif min = 0; max = 1; break; case MetadataInfo::MeteringMode: /// Int, enum from Exif min = 0; max = 6; map[255] = valueToString(255, field); break; case MetadataInfo::SubjectDistanceCategory: /// int, enum from Exif min = 0; max = 3; break; case MetadataInfo::FlashMode: /// Int, bit mask from Exif // This one is a bit special. // We return a bit mask for binary AND searching. map[0x1] = i18n("Flash has been fired"); map[0x40] = i18n("Flash with red-eye reduction mode"); //more: TODO? return map; default: qCWarning(DIGIKAM_METAENGINE_LOG) << "Unsupported field " << field << " in DMetadata::possibleValuesForEnumField"; return map; } for (int i = min ; i <= max ; ++i) { map[i] = valueToString(i, field); } return map; } bool DMetadata::hasValidField(const QVariantList& list) const { for (QVariantList::const_iterator it = list.constBegin() ; it != list.constEnd() ; ++it) { if (!(*it).isNull()) { return true; } } return false; } QVariant DMetadata::toStringListVariant(const QStringList& list) const { if (list.isEmpty()) { return QVariant(QVariant::StringList); } return list; } } // namespace Digikam diff --git a/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp b/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp index 23a53dee62..ba85161ae0 100644 --- a/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp +++ b/core/libs/metadataengine/dmetadata/dmetadata_tags.cpp @@ -1,375 +1,375 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-02-23 * Description : item metadata interface - tags helpers. * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * Copyright (C) 2011 by Leif Huhn * * 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, 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. * * ============================================================ */ #include "dmetadata.h" // Qt includes #include // Local includes #include "metaenginesettings.h" #include "digikam_version.h" #include "digikam_globals.h" #include "digikam_debug.h" namespace Digikam { bool DMetadata::getItemTagsPath(QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { for (NamespaceEntry entry : settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER())) { if (entry.isDisabled) continue; int index = 0; QString currentNamespace = entry.namespaceName; NamespaceEntry::SpecialOptions currentOpts = entry.specialOpts; // Some namespaces have altenative paths, we must search them both switch (entry.subspace) { case NamespaceEntry::XMP: while (index < 2) { const std::string myStr = currentNamespace.toStdString(); const char* nameSpace = myStr.data(); switch(currentOpts) { case NamespaceEntry::TAG_XMPBAG: tagsPath = getXmpTagStringBag(nameSpace, false); break; case NamespaceEntry::TAG_XMPSEQ: tagsPath = getXmpTagStringSeq(nameSpace, false); break; case NamespaceEntry::TAG_ACDSEE: getACDSeeTagsPath(tagsPath); break; // not used here, to suppress warnings case NamespaceEntry::COMMENT_XMP: case NamespaceEntry::COMMENT_ALTLANG: case NamespaceEntry::COMMENT_ATLLANGLIST: case NamespaceEntry::NO_OPTS: default: break; } if (!tagsPath.isEmpty()) { if (entry.separator != QLatin1String("/")) { tagsPath = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); } return true; } else if (!entry.alternativeName.isEmpty()) { currentNamespace = entry.alternativeName; currentOpts = entry.secondNameOpts; } else { break; // no alternative namespace, go to next one } index++; } break; case NamespaceEntry::IPTC: // Try to get Tags Path list from IPTC keywords. // digiKam 0.9.x has used IPTC keywords to store Tags Path list. // This way is obsolete now since digiKam support XMP because IPTC // do not support UTF-8 and have strings size limitation. But we will // let the capability to import it for interworking issues. tagsPath = getIptcKeywords(); if (!tagsPath.isEmpty()) { // Work around to Imach tags path list hosted in IPTC with '.' as separator. QStringList ntp = tagsPath.replaceInStrings(entry.separator, QLatin1String("/")); if (ntp != tagsPath) { tagsPath = ntp; //qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from Imach: " << tagsPath; } return true; } break; case NamespaceEntry::EXIF: { // Try to get Tags Path list from Exif Windows keywords. QString keyWords = getExifTagString("Exif.Image.XPKeywords", false); if (!keyWords.isEmpty()) { tagsPath = keyWords.split(entry.separator); if (!tagsPath.isEmpty()) { return true; } } break; } default: break; } } return false; } bool DMetadata::setItemTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings) const { // NOTE : with digiKam 0.9.x, we have used IPTC Keywords for that. // Now this way is obsolete, and we use XMP instead. // Set the new Tags path list. This is set, not add-to like setXmpKeywords. // Unlike the other keyword fields, we do not need to merge existing entries. QList toWrite = settings.getReadMapping(NamespaceEntry::DM_TAG_CONTAINER()); if (!settings.unifyReadWrite()) toWrite = settings.getWriteMapping(NamespaceEntry::DM_TAG_CONTAINER()); for (NamespaceEntry entry : toWrite) { if (entry.isDisabled) continue; QStringList newList; // get keywords from tags path, for type tag for (QString tagPath : tagsPath) { newList.append(tagPath.split(QLatin1Char('/')).last()); } switch(entry.subspace) { case NamespaceEntry::XMP: if (supportXmp()) { if (entry.tagPaths != NamespaceEntry::TAG) { newList = tagsPath; if (entry.separator.compare(QLatin1String("/")) != 0) { newList = newList.replaceInStrings(QLatin1String("/"), entry.separator); } } const std::string myStr = entry.namespaceName.toStdString(); const char* nameSpace = myStr.data(); switch(entry.specialOpts) { case NamespaceEntry::TAG_XMPSEQ: if (!setXmpTagStringSeq(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_XMPBAG: if (!setXmpTagStringBag(nameSpace, newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } break; case NamespaceEntry::TAG_ACDSEE: if (!setACDSeeTagsPath(newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace; return false; } default: break; } } break; case NamespaceEntry::IPTC: if (entry.namespaceName == QLatin1String("Iptc.Application2.Keywords")) { if (!setIptcKeywords(getIptcKeywords(), newList)) { qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << entry.namespaceName; return false; } } default: break; } } return true; } bool DMetadata::getACDSeeTagsPath(QStringList &tagsPath) const { // Try to get Tags Path list from ACDSee 8 Pro categories. QString xmlACDSee = getXmpTagString("Xmp.acdsee.categories", false); if (!xmlACDSee.isEmpty()) { xmlACDSee.remove(QLatin1String("")); xmlACDSee.remove(QLatin1String("")); xmlACDSee.replace(QLatin1Char('/'), QLatin1Char('\\')); QStringList xmlTags = xmlACDSee.split(QLatin1String("")); int length = tags.length() - (11 * count) - 5; if (category == 0) { tagsPath << tags.mid(5, length); } else { tagsPath.last().append(QLatin1Char('/') + tags.mid(5, length)); } category = category - count + 1; if (tags.left(5) == QLatin1String("=\"1\">") && category > 0) { tagsPath << tagsPath.last().section(QLatin1Char('/'), 0, category - 1); } } } if (!tagsPath.isEmpty()) { //qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from ACDSee: " << tagsPath; return true; } } return false; } bool DMetadata::setACDSeeTagsPath(const QStringList &tagsPath) const { // Converting Tags path list to ACDSee 8 Pro categories. const QString category(QLatin1String("")); QStringList splitTags; QStringList xmlTags; - foreach(const QString& tags, tagsPath) + foreach (const QString& tags, tagsPath) { splitTags = tags.split(QLatin1Char('/')); int current = 0; for (int index = 0; index < splitTags.size(); index++) { int tagIndex = xmlTags.indexOf(category.arg(0) + splitTags[index]); if (tagIndex == -1) { tagIndex = xmlTags.indexOf(category.arg(1) + splitTags[index]); } splitTags[index].insert(0, category.arg(index == splitTags.size() - 1 ? 1 : 0)); if (tagIndex == -1) { if (index == 0) { xmlTags << splitTags[index]; xmlTags << QLatin1String(""); current = xmlTags.size() - 1; } else { xmlTags.insert(current, splitTags[index]); xmlTags.insert(current + 1, QLatin1String("")); current++; } } else { if (index == splitTags.size() - 1) { xmlTags[tagIndex] = splitTags[index]; } current = tagIndex + 1; } } } QString xmlACDSee = QLatin1String("") + xmlTags.join(QLatin1String("")) + QLatin1String(""); //qCDebug(DIGIKAM_METAENGINE_LOG) << "xmlACDSee" << xmlACDSee; removeXmpTag("Xmp.acdsee.categories"); if (!xmlTags.isEmpty()) { if (!setXmpTagString("Xmp.acdsee.categories", xmlACDSee)) { return false; } } return true; } } // namespace Digikam diff --git a/core/libs/metadataengine/engine/metaengine_rotation.cpp b/core/libs/metadataengine/engine/metaengine_rotation.cpp index 585f7a0430..c9b6d8ca9a 100644 --- a/core/libs/metadataengine/engine/metaengine_rotation.cpp +++ b/core/libs/metadataengine/engine/metaengine_rotation.cpp @@ -1,337 +1,337 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-09-15 * Description : Exiv2 library interface. * Tools for combining rotation operations. * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ // Local includes #include "metaengine_rotation.h" namespace Digikam { /** If the picture is displayed according to the exif orientation tag, the user will request rotating operations relative to what he sees, and that is the picture rotated according to the EXIF tag. So the operation requested and the given EXIF angle must be combined. E.g. if orientation is "6" (rotate 90 clockwiseto show correctly) and the user selects 180 clockwise, the operation is 270. If the user selected 270, the operation would be None (and clearing the exif tag). This requires to describe the transformations in a model which cares for both composing (180+90=270) and eliminating (180+180=no action), as well as the non-commutative nature of the operations (vflip+90 is not 90+vflip) All 2D transformations can be described by a 2x3 matrix, see QWMetaEngineRotation. All transformations needed here - rotate 90, 180, 270, flipV, flipH - can be described in a 2x2 matrix with the values 0,1,-1 (because flipping is expressed by changing the sign only, and sine and cosine of 90, 180 and 270 are either 0,1 or -1). x' = m11 x + m12 y y' = m21 x + m22 y Moreover, all combinations of these rotate/flip operations result in one of the eight matrices defined below. (I did not proof that mathematically, but empirically) static const MetaEngineRotation identity; //( 1, 0, 0, 1) static const MetaEngineRotation rotate90; //( 0, 1, -1, 0) static const MetaEngineRotation rotate180; //(-1, 0, 0, -1) static const MetaEngineRotation rotate270; //( 0, -1, 1, 0) static const MetaEngineRotation flipHorizontal; //(-1, 0, 0, 1) static const MetaEngineRotation flipVertical; //( 1, 0, 0, -1) static const MetaEngineRotation rotate90flipHorizontal; //( 0, 1, 1, 0), first rotate, then flip static const MetaEngineRotation rotate90flipVertical; //( 0, -1, -1, 0), first rotate, then flip */ namespace Matrix { static const MetaEngineRotation identity ( 1, 0, 0, 1); static const MetaEngineRotation rotate90 ( 0, 1, -1, 0); static const MetaEngineRotation rotate180 (-1, 0, 0, -1); static const MetaEngineRotation rotate270 ( 0, -1, 1, 0); static const MetaEngineRotation flipHorizontal (-1, 0, 0, 1); static const MetaEngineRotation flipVertical ( 1, 0, 0, -1); static const MetaEngineRotation rotate90flipHorizontal ( 0, 1, 1, 0); static const MetaEngineRotation rotate90flipVertical ( 0, -1, -1, 0); MetaEngineRotation matrix(MetaEngineRotation::TransformationAction action) { switch (action) { case MetaEngineRotation::NoTransformation: return identity; case MetaEngineRotation::FlipHorizontal: return flipHorizontal; case MetaEngineRotation::FlipVertical: return flipVertical; case MetaEngineRotation::Rotate90: return rotate90; case MetaEngineRotation::Rotate180: return rotate180; case MetaEngineRotation::Rotate270: return rotate270; } return identity; } MetaEngineRotation matrix(MetaEngine::ImageOrientation exifOrientation) { switch (exifOrientation) { case MetaEngine::ORIENTATION_NORMAL: return identity; case MetaEngine::ORIENTATION_HFLIP: return flipHorizontal; case MetaEngine::ORIENTATION_ROT_180: return rotate180; case MetaEngine::ORIENTATION_VFLIP: return flipVertical; case MetaEngine::ORIENTATION_ROT_90_HFLIP: return rotate90flipHorizontal; case MetaEngine::ORIENTATION_ROT_90: return rotate90; case MetaEngine::ORIENTATION_ROT_90_VFLIP: return rotate90flipVertical; case MetaEngine::ORIENTATION_ROT_270: return rotate270; case MetaEngine::ORIENTATION_UNSPECIFIED: return identity; } return identity; } } // namespace Matrix MetaEngineRotation::MetaEngineRotation() { set( 1, 0, 0, 1 ); } MetaEngineRotation::MetaEngineRotation(TransformationAction action) { *this = Matrix::matrix(action); } MetaEngineRotation::MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation) { *this = Matrix::matrix(exifOrientation); } MetaEngineRotation::MetaEngineRotation(int m11, int m12, int m21, int m22) { set(m11, m12, m21, m22); } void MetaEngineRotation::set(int m11, int m12, int m21, int m22) { m[0][0]=m11; m[0][1]=m12; m[1][0]=m21; m[1][1]=m22; } bool MetaEngineRotation::isNoTransform() const { return (*this == Matrix::identity); } MetaEngineRotation& MetaEngineRotation::operator*=(const MetaEngineRotation& ma) { set( ma.m[0][0]*m[0][0] + ma.m[0][1]*m[1][0], ma.m[0][0]*m[0][1] + ma.m[0][1]*m[1][1], ma.m[1][0]*m[0][0] + ma.m[1][1]*m[1][0], ma.m[1][0]*m[0][1] + ma.m[1][1]*m[1][1] ); return *this; } bool MetaEngineRotation::operator==(const MetaEngineRotation& ma) const { return m[0][0]==ma.m[0][0] && m[0][1]==ma.m[0][1] && m[1][0]==ma.m[1][0] && m[1][1]==ma.m[1][1]; } bool MetaEngineRotation::operator!=(const MetaEngineRotation& ma) const { return !(*this==ma); } MetaEngineRotation& MetaEngineRotation::operator*=(TransformationAction action) { return (*this *= Matrix::matrix(action)); } MetaEngineRotation& MetaEngineRotation::operator*=(QList actions) { - foreach(const TransformationAction& action, actions) + foreach (const TransformationAction& action, actions) { *this *= Matrix::matrix(action); } return *this; } MetaEngineRotation& MetaEngineRotation::operator*=(MetaEngine::ImageOrientation exifOrientation) { return (*this *= Matrix::matrix(exifOrientation)); } /** Converts the mathematically correct description into the primitive operations that can be carried out losslessly. */ QList MetaEngineRotation::transformations() const { QList transforms; if (*this == Matrix::rotate90) { transforms << Rotate90; } else if (*this == Matrix::rotate180) { transforms << Rotate180; } else if (*this == Matrix::rotate270) { transforms << Rotate270; } else if (*this == Matrix::flipHorizontal) { transforms << FlipHorizontal; } else if (*this == Matrix::flipVertical) { transforms << FlipVertical; } else if (*this == Matrix::rotate90flipHorizontal) { //first rotate, then flip! transforms << Rotate90; transforms << FlipHorizontal; } else if (*this == Matrix::rotate90flipVertical) { //first rotate, then flip! transforms << Rotate90; transforms << FlipVertical; } return transforms; } MetaEngine::ImageOrientation MetaEngineRotation::exifOrientation() const { if (*this == Matrix::identity) { return MetaEngine::ORIENTATION_NORMAL; } if (*this == Matrix::rotate90) { return MetaEngine::ORIENTATION_ROT_90; } else if (*this == Matrix::rotate180) { return MetaEngine::ORIENTATION_ROT_180; } else if (*this == Matrix::rotate270) { return MetaEngine::ORIENTATION_ROT_270; } else if (*this == Matrix::flipHorizontal) { return MetaEngine::ORIENTATION_HFLIP; } else if (*this == Matrix::flipVertical) { return MetaEngine::ORIENTATION_VFLIP; } else if (*this == Matrix::rotate90flipHorizontal) { return MetaEngine::ORIENTATION_ROT_90_HFLIP; } else if (*this == Matrix::rotate90flipVertical) { return MetaEngine::ORIENTATION_ROT_90_VFLIP; } return MetaEngine::ORIENTATION_UNSPECIFIED; } QMatrix MetaEngineRotation::toMatrix() const { return toMatrix(exifOrientation()); } QMatrix MetaEngineRotation::toMatrix(MetaEngine::ImageOrientation orientation) { QMatrix matrix; switch (orientation) { case MetaEngine::ORIENTATION_NORMAL: case MetaEngine::ORIENTATION_UNSPECIFIED: break; case MetaEngine::ORIENTATION_HFLIP: matrix.scale(-1, 1); break; case MetaEngine::ORIENTATION_ROT_180: matrix.rotate(180); break; case MetaEngine::ORIENTATION_VFLIP: matrix.scale(1, -1); break; case MetaEngine::ORIENTATION_ROT_90_HFLIP: matrix.scale(-1, 1); matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_90: matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_90_VFLIP: matrix.scale(1, -1); matrix.rotate(90); break; case MetaEngine::ORIENTATION_ROT_270: matrix.rotate(270); break; } return matrix; } } // namespace Digikam diff --git a/core/libs/models/abstractalbummodel.cpp b/core/libs/models/abstractalbummodel.cpp index b4d8e4f8fb..e694e7b832 100644 --- a/core/libs/models/abstractalbummodel.cpp +++ b/core/libs/models/abstractalbummodel.cpp @@ -1,1221 +1,1221 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-03-23 * Description : Qt Model for Albums * * Copyright (C) 2008-2011 by Marcel Wiesweg * Copyright (C) 2010 by Andi Clemens * * 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, 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. * * ============================================================ */ #include "abstractalbummodel.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "albummodeldragdrophandler.h" #include "albumthumbnailloader.h" namespace Digikam { class Q_DECL_HIDDEN AbstractAlbumModel::Private { public: explicit Private() : rootAlbum(nullptr), addingAlbum(nullptr), type(Album::PHYSICAL), dragDropHandler(nullptr), rootBehavior(AbstractAlbumModel::IncludeRootAlbum), removingAlbum(0), itemDrag(true), itemDrop(true) { } Album* rootAlbum; Album* addingAlbum; Album::Type type; AlbumModelDragDropHandler* dragDropHandler; AbstractAlbumModel::RootAlbumBehavior rootBehavior; quintptr removingAlbum; bool itemDrag; bool itemDrop; }; AbstractAlbumModel::AbstractAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : QAbstractItemModel(parent), d(new Private) { d->type = albumType; d->rootAlbum = rootAlbum; d->rootBehavior = rootBehavior; connect(AlbumManager::instance(), SIGNAL(signalAlbumAboutToBeAdded(Album*,Album*,Album*)), this, SLOT(slotAlbumAboutToBeAdded(Album*,Album*,Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumAdded(Album*)), this, SLOT(slotAlbumAdded(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumAboutToBeDeleted(Album*)), this, SLOT(slotAlbumAboutToBeDeleted(Album*))); connect(AlbumManager::instance(), &AlbumManager::signalAlbumHasBeenDeleted, this, &AbstractAlbumModel::slotAlbumHasBeenDeleted); connect(AlbumManager::instance(), SIGNAL(signalAlbumsCleared()), this, SLOT(slotAlbumsCleared())); connect(AlbumManager::instance(), SIGNAL(signalAlbumIconChanged(Album*)), this, SLOT(slotAlbumIconChanged(Album*))); connect(AlbumManager::instance(), SIGNAL(signalAlbumRenamed(Album*)), this, SLOT(slotAlbumRenamed(Album*))); } AbstractAlbumModel::~AbstractAlbumModel() { delete d; } QVariant AbstractAlbumModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } Album* const a = static_cast(index.internalPointer()); return albumData(a, role); } QVariant AbstractAlbumModel::albumData(Album* a, int role) const { switch (role) { case Qt::DisplayRole: return a->title(); case Qt::ToolTipRole: return a->title(); case Qt::DecorationRole: // reimplement in subclasses return decorationRoleData(a); case AlbumTitleRole: return a->title(); case AlbumTypeRole: return a->type(); case AlbumPointerRole: return QVariant::fromValue(a); case AlbumIdRole: return a->id(); case AlbumGlobalIdRole: return a->globalID(); case AlbumSortRole: // reimplement in subclass return sortRoleData(a); default: return QVariant(); } } QVariant AbstractAlbumModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation) if (section == 0 && role == Qt::DisplayRole) { return columnHeader(); } return QVariant(); } int AbstractAlbumModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { Album* const a = static_cast(parent.internalPointer()); return a->childCount(); } else { if (!d->rootAlbum) { return 0; } if (d->rootBehavior == IncludeRootAlbum) { return 1; } else { return d->rootAlbum->childCount(); } } } int AbstractAlbumModel::columnCount(const QModelIndex& /*parent*/) const { return 1; } Qt::ItemFlags AbstractAlbumModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } Album* const a = static_cast(index.internalPointer()); return itemFlags(a); } bool AbstractAlbumModel::hasChildren(const QModelIndex& parent) const { if (parent.isValid()) { Album* const a = static_cast(parent.internalPointer()); return a->firstChild(); } else { if (!d->rootAlbum) { return false; } if (d->rootBehavior == IncludeRootAlbum) { return 1; } else { return d->rootAlbum->firstChild(); } } } QModelIndex AbstractAlbumModel::index(int row, int column, const QModelIndex& parent) const { if (column != 0 || row < 0) { return QModelIndex(); } if (parent.isValid()) { Album* const parentAlbum = static_cast(parent.internalPointer()); Album* const a = parentAlbum->childAtRow(row); if (a) { return createIndex(row, column, a); } } else { if (!d->rootAlbum) { return QModelIndex(); } if (d->rootBehavior == IncludeRootAlbum) { if (row == 0) { return createIndex(0, 0, d->rootAlbum); } } else { Album* const a = d->rootAlbum->childAtRow(row); if (a) { return createIndex(row, column, a); } } } return QModelIndex(); } QModelIndex AbstractAlbumModel::parent(const QModelIndex& index) const { if (index.isValid()) { Album* const a = static_cast(index.internalPointer()); return indexForAlbum(a->parent()); } return QModelIndex(); } Qt::DropActions AbstractAlbumModel::supportedDropActions() const { return Qt::CopyAction|Qt::MoveAction; } QStringList AbstractAlbumModel::mimeTypes() const { if (d->dragDropHandler) { return d->dragDropHandler->mimeTypes(); } return QStringList(); } bool AbstractAlbumModel::dropMimeData(const QMimeData*, Qt::DropAction, int, int, const QModelIndex&) { // we require custom solutions return false; } QMimeData* AbstractAlbumModel::mimeData(const QModelIndexList& indexes) const { if (!d->dragDropHandler) { return nullptr; } QList albums; - foreach(const QModelIndex& index, indexes) + foreach (const QModelIndex& index, indexes) { Album* const a = albumForIndex(index); if (a) { albums << a; } } return d->dragDropHandler->createMimeData(albums); } void AbstractAlbumModel::setEnableDrag(bool enable) { d->itemDrag = enable; } void AbstractAlbumModel::setEnableDrop(bool enable) { d->itemDrop = enable; } void AbstractAlbumModel::setDragDropHandler(AlbumModelDragDropHandler* handler) { d->dragDropHandler = handler; } AlbumModelDragDropHandler* AbstractAlbumModel::dragDropHandler() const { return d->dragDropHandler; } QModelIndex AbstractAlbumModel::indexForAlbum(Album* a) const { if (!a) { return QModelIndex(); } if (!filterAlbum(a)) { return QModelIndex(); } // a is root album? Decide on root behavior if (a == d->rootAlbum) { if (d->rootBehavior == IncludeRootAlbum) { // create top-level indexes return createIndex(0, 0, a); } else { // with this behavior, root album has no valid index return QModelIndex(); } } // Normal album. Get its row. return createIndex(a->rowFromAlbum(), 0, a); } Album* AbstractAlbumModel::albumForIndex(const QModelIndex& index) const { return (static_cast(index.internalPointer())); } Album* AbstractAlbumModel::retrieveAlbum(const QModelIndex& index) { return (index.data(AbstractAlbumModel::AlbumPointerRole).value()); } Album* AbstractAlbumModel::rootAlbum() const { return d->rootAlbum; } QModelIndex AbstractAlbumModel::rootAlbumIndex() const { return indexForAlbum(d->rootAlbum); } AbstractAlbumModel::RootAlbumBehavior AbstractAlbumModel::rootAlbumBehavior() const { return d->rootBehavior; } Album::Type AbstractAlbumModel::albumType() const { return d->type; } QVariant AbstractAlbumModel::decorationRoleData(Album*) const { return QVariant(); } QVariant AbstractAlbumModel::sortRoleData(Album* a) const { return a->title(); } QString AbstractAlbumModel::columnHeader() const { return i18n("Album"); } Qt::ItemFlags AbstractAlbumModel::itemFlags(Album*) const { Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (d->itemDrag) { f |= Qt::ItemIsDragEnabled; } if (d->itemDrop) { f |= Qt::ItemIsDropEnabled; } return f; } bool AbstractAlbumModel::filterAlbum(Album* album) const { return album && album->type() == d->type; } void AbstractAlbumModel::slotAlbumAboutToBeAdded(Album* album, Album* parent, Album* prev) { if (!filterAlbum(album)) { return; } if (album->isRoot() && d->rootBehavior == IgnoreRootAlbum) { d->rootAlbum = album; return; } // start inserting operation int row = prev ? prev->rowFromAlbum()+1 : 0; QModelIndex parentIndex = indexForAlbum(parent); beginInsertRows(parentIndex, row, row); // The root album will become available in time // when the model is instantiated before albums are initialized. // Set d->rootAlbum only after if (album->isRoot() && !d->rootAlbum) { d->rootAlbum = album; } // store album for slotAlbumAdded d->addingAlbum = album; } void AbstractAlbumModel::slotAlbumAdded(Album* album) { if (d->addingAlbum == album) { bool isRoot = (d->addingAlbum == d->rootAlbum); d->addingAlbum = nullptr; endInsertRows(); if (isRoot) { emit rootAlbumAvailable(); } } } void AbstractAlbumModel::slotAlbumAboutToBeDeleted(Album* album) { if (!filterAlbum(album)) { return; } if (album->isRoot() && d->rootBehavior == IgnoreRootAlbum) { albumCleared(album); d->rootAlbum = nullptr; return; } // begin removing operation int row = album->rowFromAlbum(); QModelIndex parent = indexForAlbum(album->parent()); beginRemoveRows(parent, row, row); albumCleared(album); // store album for slotAlbumHasBeenDeleted d->removingAlbum = reinterpret_cast(album); } void AbstractAlbumModel::slotAlbumHasBeenDeleted(quintptr p) { if (d->removingAlbum == p) { d->removingAlbum = 0; endRemoveRows(); } } void AbstractAlbumModel::slotAlbumsCleared() { d->rootAlbum = nullptr; beginResetModel(); allAlbumsCleared(); endResetModel(); } void AbstractAlbumModel::slotAlbumIconChanged(Album* album) { if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } void AbstractAlbumModel::slotAlbumRenamed(Album* album) { if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } // ------------------------------------------------------------------ AbstractSpecificAlbumModel::AbstractSpecificAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractAlbumModel(albumType, rootAlbum, rootBehavior, parent) { } void AbstractSpecificAlbumModel::setupThumbnailLoading() { AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); connect(loader, SIGNAL(signalThumbnail(Album*,QPixmap)), this, SLOT(slotGotThumbnailFromIcon(Album*,QPixmap))); connect(loader, SIGNAL(signalFailed(Album*)), this, SLOT(slotThumbnailLost(Album*))); connect(loader, SIGNAL(signalReloadThumbnails()), this, SLOT(slotReloadThumbnails())); } QString AbstractSpecificAlbumModel::columnHeader() const { return m_columnHeader; } void AbstractSpecificAlbumModel::setColumnHeader(const QString& header) { m_columnHeader = header; emit headerDataChanged(Qt::Horizontal, 0, 0); } void AbstractSpecificAlbumModel::slotGotThumbnailFromIcon(Album* album, const QPixmap&) { // see decorationRole() method of subclasses if (!filterAlbum(album)) { return; } QModelIndex index = indexForAlbum(album); emit dataChanged(index, index); } void AbstractSpecificAlbumModel::slotThumbnailLost(Album*) { // ignore, use default thumbnail } void AbstractSpecificAlbumModel::slotReloadThumbnails() { // emit dataChanged() for all albums emitDataChangedForChildren(rootAlbum()); } void AbstractSpecificAlbumModel::emitDataChangedForChildren(Album* album) { if (!album) { return; } for (Album* child = album->firstChild(); child; child = child->next()) { if (filterAlbum(child)) { // recurse to children of children emitDataChangedForChildren(child); // emit signal for child QModelIndex index = indexForAlbum(child); emit dataChanged(index, index); } } } // ------------------------------------------------------------------ class Q_DECL_HIDDEN AbstractCountingAlbumModel::Private { public: explicit Private() { showCount = false; } bool showCount; QMap countMap; QHash countHashReady; QSet includeChildrenAlbums; }; AbstractCountingAlbumModel::AbstractCountingAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractSpecificAlbumModel(albumType, rootAlbum, rootBehavior, parent), d(new Private) { } void AbstractCountingAlbumModel::setup() { connect(AlbumManager::instance(), SIGNAL(signalAlbumMoved(Album*)), this, SLOT(slotAlbumMoved(Album*))); } AbstractCountingAlbumModel::~AbstractCountingAlbumModel() { delete d; } void AbstractCountingAlbumModel::setShowCount(bool show) { if (d->showCount != show) { d->showCount = show; emitDataChangedForChildren(rootAlbum()); } } bool AbstractCountingAlbumModel::showCount() const { return d->showCount; } void AbstractCountingAlbumModel::excludeChildrenCount(const QModelIndex& index) { Album* const album = albumForIndex(index); if (!album) { return; } d->includeChildrenAlbums.remove(album->id()); updateCount(album); } void AbstractCountingAlbumModel::includeChildrenCount(const QModelIndex& index) { Album* const album = albumForIndex(index); if (!album) { return; } d->includeChildrenAlbums << album->id(); updateCount(album); } void AbstractCountingAlbumModel::setCountMap(const QMap& idCountMap) { d->countMap = idCountMap; QMap::const_iterator it = d->countMap.constBegin(); for (; it != d->countMap.constEnd(); ++it) { updateCount(albumForId(it.key())); } } void AbstractCountingAlbumModel::updateCount(Album* album) { if (!album) { return; } // if the model does not contain the album, do nothing. QModelIndex index = indexForAlbum(album); if (!index.isValid()) { return; } QHash::iterator includeIt = d->countHashReady.find(album->id()); bool changed = false; // get count for album without children int count = d->countMap.value(album->id()); // if wanted, add up children's counts if (d->includeChildrenAlbums.contains(album->id())) { AlbumIterator it(album); while ( it.current() ) { count += d->countMap.value((*it)->id()); ++it; } } // insert or update if (includeIt == d->countHashReady.end()) { changed = true; d->countHashReady[album->id()] = count; } else { changed = (includeIt.value() != count); includeIt.value() = count; } // notify views if (changed) { emit dataChanged(index, index); } } void AbstractCountingAlbumModel::setCount(Album* album, int count) { if (!album) { return; } // if the model does not contain the album, do nothing. QModelIndex index = indexForAlbum(album); if (!index.isValid()) { return; } QHash::iterator includeIt = d->countHashReady.find(album->id()); bool changed = false; // insert or update if (includeIt == d->countHashReady.end()) { changed = true; d->countHashReady[album->id()] = count; } else { changed = (includeIt.value() != count); includeIt.value() = count; } // notify views if (changed) { emit dataChanged(index, index); } } QVariant AbstractCountingAlbumModel::albumData(Album* album, int role) const { if (role == Qt::DisplayRole && d->showCount && !album->isRoot()) { if (album->isTrashAlbum()) { PAlbum* const palbum = AlbumManager::instance()->findPAlbum(album->parent()->id()); if (palbum) { QString path = palbum->folderPath(); path.append(QLatin1String(".dtrash/files")); QDir dir(path, QLatin1String(""), QDir::Unsorted, QDir::Files); return QString::fromUtf8("%1 (%2)").arg(albumName(album)).arg(dir.count()); } } else { QHash::const_iterator it = d->countHashReady.constFind(album->id()); if (it != d->countHashReady.constEnd()) { return QString::fromUtf8("%1 (%2)").arg(albumName(album)).arg(it.value()); } } } return AbstractSpecificAlbumModel::albumData(album, role); } int AbstractCountingAlbumModel::albumCount(Album* album) const { QHash::const_iterator it = d->countHashReady.constFind(album->id()); if (it != d->countHashReady.constEnd()) { return it.value(); } return -1; } QString AbstractCountingAlbumModel::albumName(Album* album) const { return album->title(); } void AbstractCountingAlbumModel::albumCleared(Album* album) { if (!AlbumManager::instance()->isMovingAlbum(album)) { d->countMap.remove(album->id()); d->countHashReady.remove(album->id()); d->includeChildrenAlbums.remove(album->id()); } } void AbstractCountingAlbumModel::allAlbumsCleared() { d->countMap.clear(); d->countHashReady.clear(); d->includeChildrenAlbums.clear(); } void AbstractCountingAlbumModel::slotAlbumMoved(Album*) { // need to update counts of all parents setCountMap(d->countMap); } // ------------------------------------------------------------------ class Q_DECL_HIDDEN AbstractCheckableAlbumModel::Private { public: explicit Private() : staticVectorContainingCheckStateRole(1, Qt::CheckStateRole) { extraFlags = nullptr; rootIsCheckable = true; addExcludeTristate = false; } Qt::ItemFlags extraFlags; bool rootIsCheckable; bool addExcludeTristate; QHash checkedAlbums; QVector staticVectorContainingCheckStateRole; }; AbstractCheckableAlbumModel::AbstractCheckableAlbumModel(Album::Type albumType, Album* const rootAlbum, RootAlbumBehavior rootBehavior, QObject* const parent) : AbstractCountingAlbumModel(albumType, rootAlbum, rootBehavior, parent), d(new Private) { setup(); } AbstractCheckableAlbumModel::~AbstractCheckableAlbumModel() { delete d; } void AbstractCheckableAlbumModel::setCheckable(bool isCheckable) { if (isCheckable) { d->extraFlags |= Qt::ItemIsUserCheckable; } else { d->extraFlags &= ~Qt::ItemIsUserCheckable; resetCheckedAlbums(); } } bool AbstractCheckableAlbumModel::isCheckable() const { return d->extraFlags & Qt::ItemIsUserCheckable; } void AbstractCheckableAlbumModel::setRootCheckable(bool isCheckable) { d->rootIsCheckable = isCheckable; Album* const root = rootAlbum(); if (!d->rootIsCheckable && root) { setChecked(root, false); } } bool AbstractCheckableAlbumModel::rootIsCheckable() const { return d->rootIsCheckable && isCheckable(); } void AbstractCheckableAlbumModel::setTristate(bool isTristate) { if (isTristate) { d->extraFlags |= Qt::ItemIsTristate; } else { d->extraFlags &= ~Qt::ItemIsTristate; } } bool AbstractCheckableAlbumModel::isTristate() const { return d->extraFlags & Qt::ItemIsTristate; } void AbstractCheckableAlbumModel::setAddExcludeTristate(bool b) { d->addExcludeTristate = b; setCheckable(true); setTristate(b); } bool AbstractCheckableAlbumModel::isAddExcludeTristate() const { return d->addExcludeTristate && isTristate(); } bool AbstractCheckableAlbumModel::isChecked(Album* album) const { return d->checkedAlbums.value(album, Qt::Unchecked) == Qt::Checked; } Qt::CheckState AbstractCheckableAlbumModel::checkState(Album* album) const { return d->checkedAlbums.value(album, Qt::Unchecked); } void AbstractCheckableAlbumModel::setChecked(Album* album, bool isChecked) { setData(indexForAlbum(album), isChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setCheckState(Album* album, Qt::CheckState state) { setData(indexForAlbum(album), state, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::toggleChecked(Album* album) { if (checkState(album) != Qt::PartiallyChecked) { setChecked(album, !isChecked(album)); } } QList AbstractCheckableAlbumModel::checkedAlbums() const { // return a list with all keys with value Qt::Checked return d->checkedAlbums.keys(Qt::Checked); } QList AbstractCheckableAlbumModel::partiallyCheckedAlbums() const { // return a list with all keys with value Qt::PartiallyChecked return d->checkedAlbums.keys(Qt::PartiallyChecked); } void AbstractCheckableAlbumModel::resetAllCheckedAlbums() { const QHash oldChecked = d->checkedAlbums; d->checkedAlbums.clear(); for (QHash::const_iterator it = oldChecked.begin() ; it != oldChecked.end() ; ++it) { if (it.value() != Qt::Unchecked) { QModelIndex index = indexForAlbum(it.key()); emit dataChanged(index, index, d->staticVectorContainingCheckStateRole); emit checkStateChanged(it.key(), Qt::Unchecked); } } } void AbstractCheckableAlbumModel::setDataForChildren(const QModelIndex& parent, const QVariant& value, int role) { setData(parent, value, role); for (int row = 0 ; row < rowCount(parent) ; ++row) { QModelIndex childIndex = index(row, 0, parent); setDataForChildren(childIndex, value, role); } } void AbstractCheckableAlbumModel::resetCheckedAlbums(const QModelIndex& parent) { if (parent == rootAlbumIndex()) { resetAllCheckedAlbums(); return; } setDataForChildren(parent, Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setDataForParents(const QModelIndex& child, const QVariant& value, int role) { QModelIndex current = child; while (current.isValid() && current != rootAlbumIndex()) { setData(current, value, role); current = parent(current); } } void AbstractCheckableAlbumModel::resetCheckedParentAlbums(const QModelIndex& child) { setDataForParents(child, Qt::Unchecked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::checkAllParentAlbums(const QModelIndex& child) { setDataForParents(child, Qt::Checked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::checkAllAlbums(const QModelIndex& parent) { setDataForChildren(parent, Qt::Checked, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::invertCheckedAlbums(const QModelIndex& parent) { Album* const album = albumForIndex(parent); if (album) { toggleChecked(album); } for (int row = 0 ; row < rowCount(parent) ; ++row) { invertCheckedAlbums(index(row, 0, parent)); } } void AbstractCheckableAlbumModel::setCheckStateForChildren(Album* album, Qt::CheckState state) { QModelIndex index = indexForAlbum(album); setDataForChildren(index, state, Qt::CheckStateRole); } void AbstractCheckableAlbumModel::setCheckStateForParents(Album* album, Qt::CheckState state) { QModelIndex index = indexForAlbum(album); setDataForParents(index, state, Qt::CheckStateRole); } QVariant AbstractCheckableAlbumModel::albumData(Album* a, int role) const { if (role == Qt::CheckStateRole) { if ((d->extraFlags & Qt::ItemIsUserCheckable) && (!a->isRoot() || d->rootIsCheckable)) { // with Qt::Unchecked as default, albums not in the hash (initially all) // are simply regarded as unchecked Qt::CheckState state = d->checkedAlbums.value(a, Qt::Unchecked); if (d->addExcludeTristate) { // Use Qt::PartiallyChecked only internally, do not expose it to the TreeView return (state == Qt::Unchecked) ? Qt::Unchecked : Qt::Checked; } return state; } } return AbstractCountingAlbumModel::albumData(a, role); } void AbstractCheckableAlbumModel::prepareAddExcludeDecoration(Album* a, QPixmap& icon) const { if (!d->addExcludeTristate) { return; } Qt::CheckState state = checkState(a); if (state != Qt::Unchecked) { int iconSize = qMax(icon.width(), icon.height()); int overlay_size = qMin(iconSize, qMax(16, iconSize * 2 / 3)); QPainter p(&icon); p.drawPixmap((icon.width() - overlay_size) / 2, (icon.height() - overlay_size) / 2, QIcon::fromTheme(state == Qt::PartiallyChecked ? QLatin1String("list-remove") : QLatin1String("list-add")).pixmap(overlay_size, overlay_size)); } } Qt::ItemFlags AbstractCheckableAlbumModel::flags(const QModelIndex& index) const { Qt::ItemFlags extraFlags = d->extraFlags; if (!d->rootIsCheckable) { QModelIndex root = rootAlbumIndex(); if (root.isValid() && index == root) { extraFlags &= ~Qt::ItemIsUserCheckable; } } return AbstractCountingAlbumModel::flags(index) | extraFlags; } bool AbstractCheckableAlbumModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::CheckStateRole) { Qt::CheckState state = (Qt::CheckState)value.toInt(); Album* const album = albumForIndex(index); if (!album) { return false; } //qCDebug(DIGIKAM_GENERAL_LOG) << "Updating check state for album" << album->title() << "to" << value; d->checkedAlbums.insert(album, state); emit dataChanged(index, index); emit checkStateChanged(album, state); return true; } else { return AbstractCountingAlbumModel::setData(index, value, role); } } void AbstractCheckableAlbumModel::albumCleared(Album* album) { // preserve check state if album is only being moved if (!AlbumManager::instance()->isMovingAlbum(album)) { d->checkedAlbums.remove(album); } AbstractCountingAlbumModel::albumCleared(album); } void AbstractCheckableAlbumModel::allAlbumsCleared() { d->checkedAlbums.clear(); AbstractCountingAlbumModel::allAlbumsCleared(); } } // namespace Digikam diff --git a/core/libs/models/categorizeditemmodel.cpp b/core/libs/models/categorizeditemmodel.cpp index ef9f77f19f..ee941d022d 100644 --- a/core/libs/models/categorizeditemmodel.cpp +++ b/core/libs/models/categorizeditemmodel.cpp @@ -1,307 +1,307 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-12-02 * Description : Generic, standard item based model for DCategorizedView * * Copyright (C) 2010-2011 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "categorizeditemmodel.h" // Qt includes #include #include #include // Local includes #include "digikam_debug.h" namespace Digikam { CategorizedItemModel::CategorizedItemModel(QObject* const parent) : QStandardItemModel(parent) { } QStandardItem* CategorizedItemModel::addItem(const QString& text, const QVariant& category, const QVariant& categorySorting) { QStandardItem* const item = new QStandardItem(text); item->setData(category, DCategorizedSortFilterProxyModel::CategoryDisplayRole); item->setData(categorySorting.isNull() ? category : categorySorting, DCategorizedSortFilterProxyModel::CategorySortRole); item->setData(rowCount(), ItemOrderRole); appendRow(item); return item; } QStandardItem* CategorizedItemModel::addItem(const QString& text, const QIcon& decoration, const QVariant& category, const QVariant& categorySorting) { QStandardItem* const item = addItem(text, category, categorySorting); item->setIcon(decoration); return item; } DCategorizedSortFilterProxyModel* CategorizedItemModel::createFilterModel() { DCategorizedSortFilterProxyModel* const filterModel = new DCategorizedSortFilterProxyModel(this); filterModel->setCategorizedModel(true); filterModel->setSortRole(ItemOrderRole); filterModel->setSourceModel(this); return filterModel; } // ------------------------------------------------------------------------------------------------------------------------- static QString adjustedActionText(const QAction* const action) { QString text = action->text(); text.remove(QLatin1Char('&')); text.remove(QLatin1String(" ...")); text.remove(QLatin1String("...")); int slashPos = -1; while ( (slashPos = text.indexOf(QLatin1Char('/'), slashPos + 1)) != -1 ) { if (slashPos == 0 || slashPos == text.length()-1) continue; if (text.at(slashPos - 1).isSpace() || text.at(slashPos + 1).isSpace()) continue; text.replace(slashPos, 1, QLatin1Char(',')); text.insert(slashPos+1, QLatin1Char(' ')); } return text; } class Q_DECL_HIDDEN ActionEnumerator { public: explicit ActionEnumerator(const QList& whitelist) : whitelist(whitelist) { } void enumerate(QWidget* const w) { // recurse enumerateActions(w, nullptr); } void addTo(ActionItemModel* const model, ActionItemModel::MenuCategoryMode mode) { int categorySortStartIndex = model->rowCount(); - foreach(QAction* const a, actions) + foreach (QAction* const a, actions) { QAction* categoryAction = nullptr; if (mode & ActionItemModel::ToplevelMenuCategory) { for (QAction* p = a; p; p = parents.value(p)) categoryAction = p; } else { categoryAction = parents.value(a); } if (!categoryAction) continue; QVariant categorySortValue; if (mode & ActionItemModel::SortCategoriesByInsertionOrder) { categorySortValue = categorySortStartIndex++; } model->addAction(a, adjustedActionText(categoryAction), categorySortValue); } } protected: const QList& whitelist; QList actions; QMap parents; QList parentsInOrder; void enumerateActions(const QWidget* const w, QAction* const widgetAction) { - foreach(QAction* const a, w->actions()) + foreach (QAction* const a, w->actions()) { if (a->menu()) { enumerateActions(a->menu(), a->menu()->menuAction()); } else if (whitelist.isEmpty() || whitelist.contains(a)) { actions << a; } parents[a] = widgetAction; if (!parentsInOrder.contains(widgetAction)) { parentsInOrder << widgetAction; } } } }; // ------------------------------------------------------------------------------------------------------------------------- ActionItemModel::ActionItemModel(QObject* const parent) : CategorizedItemModel(parent), m_mode(ToplevelMenuCategory | SortCategoriesAlphabetically), m_filterModel(nullptr) { } void ActionItemModel::setMode(MenuCategoryMode mode) { m_mode = mode; } ActionItemModel::MenuCategoryMode ActionItemModel::mode() const { return m_mode; } QStandardItem* ActionItemModel::addAction(QAction* action, const QString& category, const QVariant& categorySorting) { QStandardItem* const item = addItem(QString(), category, categorySorting); item->setEditable(false); setPropertiesFromAction(item, action); connect(action, SIGNAL(changed()), this, SLOT(slotActionChanged())); return item; } DCategorizedSortFilterProxyModel* ActionItemModel::createFilterModel() { DCategorizedSortFilterProxyModel* const filterModel = new ActionSortFilterProxyModel(this); filterModel->setCategorizedModel(true); filterModel->setSortRole(ItemOrderRole); filterModel->setSourceModel(this); m_filterModel = filterModel; return filterModel; } void ActionItemModel::setPropertiesFromAction(QStandardItem* item, QAction* action) { item->setText(adjustedActionText(action)); item->setIcon(action->icon()); item->setEnabled(action->isEnabled()); item->setCheckable(action->isCheckable()); if (action->toolTip() != action->text()) item->setToolTip(action->toolTip()); item->setWhatsThis(action->whatsThis()); item->setData(QVariant::fromValue(static_cast(action)), ItemActionRole); } void ActionItemModel::addActions(QWidget* w) { addActions(w, QList()); } void ActionItemModel::addActions(QWidget* w, const QList& actionWhiteList) { ActionEnumerator enumerator(actionWhiteList); enumerator.enumerate(w); enumerator.addTo(this, m_mode); } QAction* ActionItemModel::actionForIndex(const QModelIndex& index) { return static_cast(index.data(ItemActionRole).value()); } QStandardItem* ActionItemModel::itemForAction(QAction* action) const { if (!action) return nullptr; for (int i = 0 ; i < rowCount() ; ++i) { QStandardItem* const it = item(i); if (it && (static_cast(it->data(ItemActionRole).value()) == action)) return it; } return nullptr; } QModelIndex ActionItemModel::indexForAction(QAction *action) const { return indexFromItem(itemForAction(action)); } void ActionItemModel::hover(const QModelIndex& index) { QAction* const action = actionForIndex(index); if (action) action->hover(); } void ActionItemModel::toggle(const QModelIndex& index) { QAction* const action = actionForIndex(index); if (action) action->toggle(); } void ActionItemModel::trigger(const QModelIndex& index) { QAction* const action = actionForIndex(index); if (action && action->isEnabled()) action->trigger(); } void ActionItemModel::slotActionChanged() { QAction* const action = qobject_cast(sender()); QStandardItem* const item = itemForAction(action); if (item) { setPropertiesFromAction(item, action); } if (m_filterModel) { m_filterModel->invalidate(); } } } // namespace Digikam diff --git a/core/libs/notificationmanager/dnotificationpopup.cpp b/core/libs/notificationmanager/dnotificationpopup.cpp index 00ca85ce35..f47d9ed129 100644 --- a/core/libs/notificationmanager/dnotificationpopup.cpp +++ b/core/libs/notificationmanager/dnotificationpopup.cpp @@ -1,710 +1,710 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-03 * Description : dialog-like popup that displays messages without interrupting the user * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2001-2006 by Richard Moore * Copyright (C) 2004-2005 by Sascha Cunz * * 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, 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. * * ============================================================ */ #include "dnotificationpopup.h" #include "digikam_config.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Digikam { static const int DEFAULT_POPUP_TYPE = DNotificationPopup::Boxed; static const int DEFAULT_POPUP_TIME = 6 * 1000; static const Qt::WindowFlags POPUP_FLAGS = Qt::Tool | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint; class Q_DECL_HIDDEN DNotificationPopup::Private { public: explicit Private(DNotificationPopup* const q, WId winId) : q(q), popupStyle(DEFAULT_POPUP_TYPE), window(winId), msgView(nullptr), topLayout(nullptr), hideDelay(DEFAULT_POPUP_TIME), hideTimer(new QTimer(q)), ttlIcon(nullptr), ttl(nullptr), msg(nullptr), autoDelete(false) { q->setWindowFlags(POPUP_FLAGS); q->setFrameStyle(QFrame::Box | QFrame::Plain); q->setLineWidth(2); if (popupStyle == Boxed) { q->setFrameStyle(QFrame::Box | QFrame::Plain); q->setLineWidth(2); } else if (popupStyle == Balloon) { q->setPalette(QToolTip::palette()); } connect(hideTimer, SIGNAL(timeout()), q, SLOT(hide())); connect(q, SIGNAL(clicked()), q, SLOT(hide())); } public: DNotificationPopup* q; int popupStyle; QPolygon surround; QPoint anchor; QPoint fixedPosition; WId window; QWidget* msgView; QBoxLayout* topLayout; int hideDelay; QTimer* hideTimer; QLabel* ttlIcon; QLabel* ttl; QLabel* msg; bool autoDelete; /** * Updates the transparency mask. Unused if PopupStyle == Boxed */ void updateMask() { // get screen-geometry for screen our anchor is on // (geometry can differ from screen to screen! QRect deskRect = desktopRectForPoint(anchor); const int width = q->width(); const int height = q->height(); int xh = 70, xl = 40; if (width < 80) { xh = xl = 40; } else if (width < 110) { xh = width - 40; } bool bottom = (anchor.y() + height) > ((deskRect.y() + deskRect.height() - 48)); bool right = (anchor.x() + width) > ((deskRect.x() + deskRect.width() - 48)); QPoint corners[4] = { QPoint(width - 50, 10), QPoint(10, 10), QPoint(10, height - 50), QPoint(width - 50, height - 50) }; QBitmap mask(width, height); mask.clear(); QPainter p(&mask); QBrush brush(Qt::color1, Qt::SolidPattern); p.setBrush(brush); int i = 0, z = 0; for ( ; i < 4 ; ++i) { QPainterPath path; path.moveTo(corners[i].x(), corners[i].y()); path.arcTo(corners[i].x(), corners[i].y(), 40, 40, i * 90, 90); QPolygon corner = path.toFillPolygon().toPolygon(); surround.resize(z + corner.count() - 1); for (int s = 1 ; s < corner.count() - 1 ; s++, z++) // krazy:exclude=postfixop { surround.setPoint(z, corner[s]); } if (bottom && i == 2) { if (right) { surround.resize(z + 3); surround.setPoint(z++, QPoint(width - xh, height - 10)); surround.setPoint(z++, QPoint(width - 20, height)); surround.setPoint(z++, QPoint(width - xl, height - 10)); } else { surround.resize(z + 3); surround.setPoint(z++, QPoint(xl, height - 10)); surround.setPoint(z++, QPoint(20, height)); surround.setPoint(z++, QPoint(xh, height - 10)); } } else if (!bottom && i == 0) { if (right) { surround.resize(z + 3); surround.setPoint(z++, QPoint(width - xl, 10)); surround.setPoint(z++, QPoint(width - 20, 0)); surround.setPoint(z++, QPoint(width - xh, 10)); } else { surround.resize(z + 3); surround.setPoint(z++, QPoint(xh, 10)); surround.setPoint(z++, QPoint(20, 0)); surround.setPoint(z++, QPoint(xl, 10)); } } } surround.resize(z + 1); surround.setPoint(z, surround[0]); p.drawPolygon(surround); q->setMask(mask); q->move(right ? anchor.x() - width + 20 : (anchor.x() < 11 ? 11 : anchor.x() - 20), bottom ? anchor.y() - height : (anchor.y() < 11 ? 11 : anchor.y() )); q->update(); } /** * Calculates the position to place the popup near the specified rectangle. */ QPoint calculateNearbyPoint(const QRect& target) { QPoint pos = target.topLeft(); int x = pos.x(); int y = pos.y(); int w = q->minimumSizeHint().width(); int h = q->minimumSizeHint().height(); QRect r = desktopRectForPoint(QPoint(x + w / 2, y + h / 2)); if (popupStyle == Balloon) { // find a point to anchor to if (x + w > r.width()) { x = x + target.width(); } if (y + h > r.height()) { y = y + target.height(); } } else { if (x < r.center().x()) { x = x + target.width(); } else { x = x - w; } // It's apparently trying to go off screen, so display it ALL at the bottom. if ((y + h) > r.bottom()) { y = r.bottom() - h; } if ((x + w) > r.right()) { x = r.right() - w; } } if (y < r.top()) { y = r.top(); } if (x < r.left()) { x = r.left(); } return QPoint(x, y); } QRect desktopRectForPoint(const QPoint& point) { QList screens = QGuiApplication::screens(); - foreach(const QScreen* screen, screens) + foreach (const QScreen* screen, screens) { if (screen->geometry().contains(point)) { return screen->geometry(); } } // If no screen was found, return the primary screen's geometry return QGuiApplication::primaryScreen()->geometry(); } }; DNotificationPopup::DNotificationPopup(QWidget* const parent, Qt::WindowFlags f) : QFrame(nullptr, f ? f : POPUP_FLAGS), d(new Private(this, parent ? parent->effectiveWinId() : 0L)) { } DNotificationPopup::DNotificationPopup(WId win) : QFrame(nullptr), d(new Private(this, win)) { } DNotificationPopup::~DNotificationPopup() { delete d; } void DNotificationPopup::setPopupStyle(int popupstyle) { if (d->popupStyle == popupstyle) { return; } d->popupStyle = popupstyle; if (d->popupStyle == Boxed) { setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(2); } else if (d->popupStyle == Balloon) { setPalette(QToolTip::palette()); } } void DNotificationPopup::setView(QWidget* child) { delete d->msgView; d->msgView = child; delete d->topLayout; d->topLayout = new QVBoxLayout(this); if (d->popupStyle == Balloon) { const int marginHint = style()->pixelMetric(QStyle::PM_DefaultChildMargin); d->topLayout->setMargin(2 * marginHint); } d->topLayout->addWidget(d->msgView); d->topLayout->activate(); } void DNotificationPopup::setView(const QString& caption, const QString& text, const QPixmap& icon) { // qCDebug(LOG_KNOTIFICATIONS) << "DNotificationPopup::setView " << caption << ", " << text; setView(standardView(caption, text, icon, this)); } QWidget* DNotificationPopup::standardView(const QString& caption, const QString& text, const QPixmap& icon, QWidget* parent) { QWidget* const top = new QWidget(parent ? parent : this); QVBoxLayout* const vb = new QVBoxLayout(top); vb->setContentsMargins(0, 0, 0, 0); top->setLayout(vb); QHBoxLayout* hb = nullptr; if (!icon.isNull()) { hb = new QHBoxLayout(top); hb->setContentsMargins(0, 0, 0, 0); vb->addLayout(hb); d->ttlIcon = new QLabel(top); d->ttlIcon->setPixmap(icon); d->ttlIcon->setAlignment(Qt::AlignLeft); hb->addWidget(d->ttlIcon); } if (!caption.isEmpty()) { d->ttl = new QLabel(caption, top); QFont fnt = d->ttl->font(); fnt.setBold(true); d->ttl->setFont(fnt); d->ttl->setAlignment(Qt::AlignHCenter); if (hb) { hb->addWidget(d->ttl); hb->setStretchFactor(d->ttl, 10); // enforce centering } else { vb->addWidget(d->ttl); } } if (!text.isEmpty()) { d->msg = new QLabel(text, top); d->msg->setAlignment(Qt::AlignLeft); d->msg->setTextInteractionFlags(Qt::LinksAccessibleByMouse); d->msg->setOpenExternalLinks(true); vb->addWidget(d->msg); } return top; } void DNotificationPopup::setView(const QString& caption, const QString& text) { setView(caption, text, QPixmap()); } QWidget* DNotificationPopup::view() const { return d->msgView; } int DNotificationPopup::timeout() const { return d->hideDelay; } void DNotificationPopup::setTimeout(int delay) { d->hideDelay = (delay < 0) ? DEFAULT_POPUP_TIME : delay; if (d->hideTimer->isActive()) { if (delay) { d->hideTimer->start(delay); } else { d->hideTimer->stop(); } } } bool DNotificationPopup::autoDelete() const { return d->autoDelete; } void DNotificationPopup::setAutoDelete(bool autoDelete) { d->autoDelete = autoDelete; } void DNotificationPopup::mouseReleaseEvent(QMouseEvent* e) { emit clicked(); emit clicked(e->pos()); } void DNotificationPopup::setVisible(bool visible) { if (!visible) { QFrame::setVisible(visible); return; } if (size() != sizeHint()) { resize(sizeHint()); } if (d->fixedPosition.isNull()) { positionSelf(); } else { if (d->popupStyle == Balloon) { setAnchor(d->fixedPosition); } else { move(d->fixedPosition); } } QFrame::setVisible(/*visible=*/ true); int delay = d->hideDelay; if (delay < 0) { delay = DEFAULT_POPUP_TIME; } if (delay > 0) { d->hideTimer->start(delay); } } void DNotificationPopup::show(const QPoint& p) { d->fixedPosition = p; show(); } void DNotificationPopup::hideEvent(QHideEvent*) { d->hideTimer->stop(); if (d->autoDelete) { deleteLater(); } } QPoint DNotificationPopup::defaultLocation() const { const QRect r = QGuiApplication::primaryScreen()->availableGeometry(); return QPoint(r.left(), r.top()); } void DNotificationPopup::positionSelf() { QRect target; if (d->window) { // Put it by the window itself if (target.isNull()) { // Avoid making calls to the window system if we can QWidget* const widget = QWidget::find(d->window); if (widget) { target = widget->geometry(); } } } if (target.isNull()) { target = QRect(defaultLocation(), QSize(0, 0)); } moveNear(target); } void DNotificationPopup::moveNear(const QRect& target) { QPoint pos = d->calculateNearbyPoint(target); if (d->popupStyle == Balloon) { setAnchor(pos); } else { move(pos.x(), pos.y()); } } QPoint DNotificationPopup::anchor() const { return d->anchor; } void DNotificationPopup::setAnchor(const QPoint& anchor) { d->anchor = anchor; d->updateMask(); } void DNotificationPopup::paintEvent(QPaintEvent* pe) { if (d->popupStyle == Balloon) { QPainter p; p.begin(this); p.drawPolygon(d->surround); } else { QFrame::paintEvent(pe); } } DNotificationPopup* DNotificationPopup::message(const QString& caption, const QString& text, const QPixmap& icon, QWidget* parent, int timeout, const QPoint& p) { return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p); } DNotificationPopup* DNotificationPopup::message(const QString& text, QWidget* parent, const QPoint& p) { return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent, -1, p); } DNotificationPopup* DNotificationPopup::message(const QString& caption, const QString& text, QWidget* parent, const QPoint& p) { return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent, -1, p); } DNotificationPopup* DNotificationPopup::message(const QString& caption, const QString& text, const QPixmap& icon, WId parent, int timeout, const QPoint& p) { return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout, p); } DNotificationPopup* DNotificationPopup::message(const QString& caption, const QString& text, const QPixmap& icon, QSystemTrayIcon* parent, int timeout) { return message(DEFAULT_POPUP_TYPE, caption, text, icon, parent, timeout); } DNotificationPopup* DNotificationPopup::message(const QString& text, QSystemTrayIcon* parent) { return message(DEFAULT_POPUP_TYPE, QString(), text, QPixmap(), parent); } DNotificationPopup* DNotificationPopup::message(const QString& caption, const QString& text, QSystemTrayIcon* parent) { return message(DEFAULT_POPUP_TYPE, caption, text, QPixmap(), parent); } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& caption, const QString& text, const QPixmap& icon, QWidget* parent, int timeout, const QPoint& p) { DNotificationPopup* const pop = new DNotificationPopup(parent); pop->setPopupStyle(popupStyle); pop->setAutoDelete(true); pop->setView(caption, text, icon); pop->d->hideDelay = (timeout < 0) ? DEFAULT_POPUP_TIME : timeout; if (p.isNull()) { pop->show(); } else { pop->show(p); } return pop; } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& text, QWidget* parent, const QPoint& p) { return message(popupStyle, QString(), text, QPixmap(), parent, -1, p); } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& caption, const QString& text, QWidget* parent, const QPoint& p) { return message(popupStyle, caption, text, QPixmap(), parent, -1, p); } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& caption, const QString& text, const QPixmap& icon, WId parent, int timeout, const QPoint& p) { DNotificationPopup* const pop = new DNotificationPopup(parent); pop->setPopupStyle(popupStyle); pop->setAutoDelete(true); pop->setView(caption, text, icon); pop->d->hideDelay = (timeout < 0) ? DEFAULT_POPUP_TIME : timeout; if (p.isNull()) { pop->show(); } else { pop->show(p); } return pop; } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& caption, const QString& text, const QPixmap& icon, QSystemTrayIcon* parent, int timeout) { DNotificationPopup* const pop = new DNotificationPopup(); pop->setPopupStyle(popupStyle); pop->setAutoDelete(true); pop->setView(caption, text, icon); pop->d->hideDelay = (timeout < 0) ? DEFAULT_POPUP_TIME : timeout; QPoint pos = pop->d->calculateNearbyPoint(parent->geometry()); pop->show(pos); pop->moveNear(parent->geometry()); return pop; } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& text, QSystemTrayIcon* parent) { return message(popupStyle, QString(), text, QPixmap(), parent); } DNotificationPopup* DNotificationPopup::message(int popupStyle, const QString& caption, const QString& text, QSystemTrayIcon* parent) { return message(popupStyle, caption, text, QPixmap(), parent); } } // namespace Digikam diff --git a/core/libs/notificationmanager/dnotificationwidget_p.cpp b/core/libs/notificationmanager/dnotificationwidget_p.cpp index eb83a8ebeb..105b99e630 100644 --- a/core/libs/notificationmanager/dnotificationwidget_p.cpp +++ b/core/libs/notificationmanager/dnotificationwidget_p.cpp @@ -1,235 +1,235 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-07-03 * Description : A widget to provide feedback or propose opportunistic interactions * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (c) 2011 by Aurelien Gateau * Copyright (c) 2014 by Dominik Haumann * * 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, 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. * * ============================================================ */ #include "dnotificationwidget_p.h" // Qt includes #include #include #include #include #include #include #include #include #include #include // KDE includes #include namespace Digikam { DNotificationWidget::Private::Private(DNotificationWidget* const q_ptr) : QObject(q_ptr) { q = q_ptr; content = nullptr; iconLabel = nullptr; textLabel = nullptr; closeButton = nullptr; timeLine = nullptr; messageType = DNotificationWidget::Information; wordWrap = false; } DNotificationWidget::Private::~Private() { } void DNotificationWidget::Private::init() { q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); timeLine = new QTimeLine(500, q); connect(timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(slotTimeLineChanged(qreal))); connect(timeLine, SIGNAL(finished()), this, SLOT(slotTimeLineFinished())); content = new QFrame(q); content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); wordWrap = false; iconLabel = new QLabel(content); iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); iconLabel->hide(); textLabel = new QLabel(content); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); connect(textLabel, &QLabel::linkActivated, q, &DNotificationWidget::linkActivated); connect(textLabel, &QLabel::linkHovered, q, &DNotificationWidget::linkHovered); QAction* const closeAction = new QAction(q); closeAction->setText(i18n("&Close")); closeAction->setToolTip(i18n("Close message")); closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton)); connect(closeAction, &QAction::triggered, q, &DNotificationWidget::animatedHide); closeButton = new QToolButton(content); closeButton->setAutoRaise(true); closeButton->setDefaultAction(closeAction); q->setMessageType(DNotificationWidget::Information); } void DNotificationWidget::Private::createLayout() { delete content->layout(); content->resize(q->size()); qDeleteAll(buttons); buttons.clear(); - foreach(QAction* const action, q->actions()) + foreach (QAction* const action, q->actions()) { QToolButton* const button = new QToolButton(content); button->setDefaultAction(action); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); buttons.append(button); } // AutoRaise reduces visual clutter, but we don't want to turn it on if // there are other buttons, otherwise the close button will look different // from the others. closeButton->setAutoRaise(buttons.isEmpty()); if (wordWrap) { QGridLayout* const layout = new QGridLayout(content); // Set alignment to make sure icon does not move down if text wraps layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop); layout->addWidget(textLabel, 0, 1); QHBoxLayout* const buttonLayout = new QHBoxLayout; buttonLayout->addStretch(); - foreach(QToolButton* const button, buttons) + foreach (QToolButton* const button, buttons) { // For some reason, calling show() is necessary if wordwrap is true, // otherwise the buttons do not show up. It is not needed if // wordwrap is false. button->show(); buttonLayout->addWidget(button); } buttonLayout->addWidget(closeButton); layout->addItem(buttonLayout, 1, 0, 1, 2); } else { QHBoxLayout* const layout = new QHBoxLayout(content); layout->addWidget(iconLabel); layout->addWidget(textLabel); - foreach(QToolButton* const button, buttons) + foreach (QToolButton* const button, buttons) { layout->addWidget(button); } layout->addWidget(closeButton); }; if (q->isVisible()) { q->setFixedHeight(content->sizeHint().height()); } q->updateGeometry(); } void DNotificationWidget::Private::updateLayout() { if (content->layout()) { createLayout(); } } void DNotificationWidget::Private::updateSnapShot() { // Attention: updateSnapShot calls QWidget::render(), which causes the whole // window layouts to be activated. Calling this method from resizeEvent() // can lead to infinite recursion, see: // https://bugs.kde.org/show_bug.cgi?id=311336 contentSnapShot = QPixmap(content->size() * q->devicePixelRatio()); contentSnapShot.setDevicePixelRatio(q->devicePixelRatio()); contentSnapShot.fill(Qt::transparent); content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren); } void DNotificationWidget::Private::slotTimeLineChanged(qreal value) { q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height()); q->update(); } void DNotificationWidget::Private::slotTimeLineFinished() { if (timeLine->direction() == QTimeLine::Forward) { // Show // We set the whole geometry here, because it may be wrong if a // DNotificationWidget is shown right when the toplevel window is created. content->setGeometry(0, 0, q->width(), bestContentHeight()); // notify about finished animation emit q->showAnimationFinished(); } else { // hide and notify about finished animation q->hide(); emit q->hideAnimationFinished(); } } int DNotificationWidget::Private::bestContentHeight() const { int height = content->heightForWidth(q->width()); if (height == -1) { height = content->sizeHint().height(); } return height; } } // namespace Digikam diff --git a/core/libs/pgfutils/libpgf/Decoder.cpp b/core/libs/pgfutils/libpgf/Decoder.cpp index 34078b04aa..e003343104 100644 --- a/core/libs/pgfutils/libpgf/Decoder.cpp +++ b/core/libs/pgfutils/libpgf/Decoder.cpp @@ -1,1055 +1,1055 @@ /* * The Progressive Graphics File; http://www.libpgf.org * * $Date: 2006-06-04 22:05:59 +0200 (So, 04 Jun 2006) $ * $Revision: 229 $ * * This file Copyright (C) 2006 xeraina GmbH, Switzerland * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE * as published by the Free Software Foundation; either version 2.1 * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ ////////////////////////////////////////////////////////////////////// /// @file Decoder.cpp /// @brief PGF decoder class implementation /// @author C. Stamm, R. Spuler #include "Decoder.h" #ifdef TRACE #include #endif #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wextra" #endif #if defined(__APPLE__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wextra" #endif ////////////////////////////////////////////////////// // PGF: file structure // // PGFPreHeader PGFHeader [PGFPostHeader] LevelLengths Level_n-1 Level_n-2 ... Level_0 // PGFPostHeader ::= [ColorTable] [UserData] // LevelLengths ::= UINT32[nLevels] ////////////////////////////////////////////////////// // Decoding scheme // input: binary file // output: wavelet coefficients stored in subbands // // file (for each buffer: packedLength (16 bit), packed bits) // | // m_codeBuffer (for each plane: RLcodeLength (16 bit), RLcoded sigBits + m_sign, refBits) // | | | // m_sign sigBits refBits [BufferLen, BufferLen, BufferLen] // | | | // m_value [BufferSize] // | // subband // // Constants #define CodeBufferBitLen (CodeBufferLen*WordWidth) ///< max number of bits in m_codeBuffer #define MaxCodeLen ((1 << RLblockSizeLen) - 1) ///< max length of RL encoded block ///////////////////////////////////////////////////////////////////// /// Constructor /// Read pre-header, header, and levelLength /// It might throw an IOException. /// @param stream A PGF stream /// @param preHeader [out] A PGF pre-header /// @param header [out] A PGF header /// @param postHeader [out] A PGF post-header /// @param levelLength The location of the levelLength array. The array is allocated in this method. The caller has to delete this array. /// @param userDataPos The stream position of the user data (metadata) /// @param useOMP If true, then the decoder will use multi-threading based on openMP /// @param userDataPolicy Policy of user data (meta-data) handling while reading PGF headers. CDecoder::CDecoder(CPGFStream* stream, PGFPreHeader& preHeader, PGFHeader& header, PGFPostHeader& postHeader, UINT32*& levelLength, UINT64& userDataPos, bool useOMP, UINT32 userDataPolicy) : m_stream(stream) , m_startPos(0) , m_streamSizeEstimation(0) , m_encodedHeaderLength(0) , m_currentBlockIndex(0) , m_macroBlocksAvailable(0) #ifdef __PGFROISUPPORT__ , m_roi(false) #endif { ASSERT(m_stream); int count, expected; // store current stream position m_startPos = m_stream->GetPos(); // read magic and version count = expected = MagicVersionSize; m_stream->Read(&count, &preHeader); if (count != expected) ReturnWithError(MissingData); // read header size if (preHeader.version & Version6) { // 32 bit header size since version 6 count = expected = 4; } else { count = expected = 2; } m_stream->Read(&count, ((UINT8*)&preHeader) + MagicVersionSize); if (count != expected) ReturnWithError(MissingData); // make sure the values are correct read preHeader.hSize = __VAL(preHeader.hSize); // check magic number if (memcmp(preHeader.magic, PGFMagic, 3) != 0) { // error condition: wrong Magic number ReturnWithError(FormatCannotRead); } // read file header count = expected = (preHeader.hSize < HeaderSize) ? preHeader.hSize : HeaderSize; m_stream->Read(&count, &header); if (count != expected) ReturnWithError(MissingData); // make sure the values are correct read header.height = __VAL(UINT32(header.height)); header.width = __VAL(UINT32(header.width)); // be ready to read all versions including version 0 if (preHeader.version > 0) { #ifndef __PGFROISUPPORT__ // check ROI usage if (preHeader.version & PGFROI) ReturnWithError(FormatCannotRead); #endif UINT32 size = preHeader.hSize; if (size > HeaderSize) { size -= HeaderSize; count = 0; // read post-header if (header.mode == ImageModeIndexedColor) { if (size < ColorTableSize) ReturnWithError(FormatCannotRead); // read color table count = expected = ColorTableSize; m_stream->Read(&count, postHeader.clut); if (count != expected) ReturnWithError(MissingData); } if (size > (UINT32)count) { size -= count; // read/skip user data UserdataPolicy policy = (UserdataPolicy)((userDataPolicy <= MaxUserDataSize) ? UP_CachePrefix : 0xFFFFFFFF - userDataPolicy); userDataPos = m_stream->GetPos(); postHeader.userDataLen = size; if (policy == UP_Skip) { postHeader.cachedUserDataLen = 0; postHeader.userData = nullptr; Skip(size); } else { postHeader.cachedUserDataLen = (policy == UP_CachePrefix) ? __min(size, userDataPolicy) : size; // create user data memory block postHeader.userData = new(std::nothrow) UINT8[postHeader.cachedUserDataLen]; if (!postHeader.userData) ReturnWithError(InsufficientMemory); // read user data count = expected = postHeader.cachedUserDataLen; m_stream->Read(&count, postHeader.userData); if (count != expected) ReturnWithError(MissingData); // skip remaining user data if (postHeader.cachedUserDataLen < size) Skip(size - postHeader.cachedUserDataLen); } } } // create levelLength levelLength = new(std::nothrow) UINT32[header.nLevels]; if (!levelLength) ReturnWithError(InsufficientMemory); // read levelLength count = expected = header.nLevels*WordBytes; m_stream->Read(&count, levelLength); if (count != expected) ReturnWithError(MissingData); #ifdef PGF_USE_BIG_ENDIAN // make sure the values are correct read for (int i=0; i < header.nLevels; i++) { levelLength[i] = __VAL(levelLength[i]); } #endif // compute the total size in bytes; keep attention: level length information is optional for (int i=0; i < header.nLevels; i++) { m_streamSizeEstimation += levelLength[i]; } } // store current stream position m_encodedHeaderLength = UINT32(m_stream->GetPos() - m_startPos); // set number of threads #ifdef LIBPGF_USE_OPENMP m_macroBlockLen = omp_get_num_procs(); #else m_macroBlockLen = 1; #endif if (useOMP && m_macroBlockLen > 1) { #ifdef LIBPGF_USE_OPENMP omp_set_num_threads(m_macroBlockLen); #endif // create macro block array m_macroBlocks = new(std::nothrow) CMacroBlock*[m_macroBlockLen]; if (!m_macroBlocks) ReturnWithError(InsufficientMemory); for (int i = 0; i < m_macroBlockLen; i++) m_macroBlocks[i] = new CMacroBlock(); m_currentBlock = m_macroBlocks[m_currentBlockIndex]; } else { m_macroBlocks = 0; m_macroBlockLen = 1; // there is only one macro block m_currentBlock = new(std::nothrow) CMacroBlock(); if (!m_currentBlock) ReturnWithError(InsufficientMemory); } } ///////////////////////////////////////////////////////////////////// // Destructor CDecoder::~CDecoder() { if (m_macroBlocks) { for (int i=0; i < m_macroBlockLen; i++) delete m_macroBlocks[i]; delete[] m_macroBlocks; } else { delete m_currentBlock; } } ////////////////////////////////////////////////////////////////////// /// Copies data from the open stream to a target buffer. /// It might throw an IOException. /// @param target The target buffer /// @param len The number of bytes to read /// @return The number of bytes copied to the target buffer UINT32 CDecoder::ReadEncodedData(UINT8* target, UINT32 len) const { ASSERT(m_stream); int count = len; m_stream->Read(&count, target); return count; } ///////////////////////////////////////////////////////////////////// /// Unpartitions a rectangular region of a given subband. /// Partitioning scheme: The plane is partitioned in squares of side length LinBlockSize. /// Read wavelet coefficients from the output buffer of a macro block. /// It might throw an IOException. /// @param band A subband /// @param quantParam Dequantization value /// @param width The width of the rectangle /// @param height The height of the rectangle /// @param startPos The relative subband position of the top left corner of the rectangular region /// @param pitch The number of bytes in row of the subband void CDecoder::Partition(CSubband* band, int quantParam, int width, int height, int startPos, int pitch) { ASSERT(band); const div_t ww = div(width, LinBlockSize); const div_t hh = div(height, LinBlockSize); const int ws = pitch - LinBlockSize; const int wr = pitch - ww.rem; int pos, base = startPos, base2; // main height for (int i=0; i < hh.quot; i++) { // main width base2 = base; for (int j=0; j < ww.quot; j++) { pos = base2; for (int y=0; y < LinBlockSize; y++) { for (int x=0; x < LinBlockSize; x++) { DequantizeValue(band, pos, quantParam); pos++; } pos += ws; } base2 += LinBlockSize; } // rest of width pos = base2; for (int y=0; y < LinBlockSize; y++) { for (int x=0; x < ww.rem; x++) { DequantizeValue(band, pos, quantParam); pos++; } pos += wr; base += pitch; } } // main width base2 = base; for (int j=0; j < ww.quot; j++) { // rest of height pos = base2; for (int y=0; y < hh.rem; y++) { for (int x=0; x < LinBlockSize; x++) { DequantizeValue(band, pos, quantParam); pos++; } pos += ws; } base2 += LinBlockSize; } // rest of height pos = base2; for (int y=0; y < hh.rem; y++) { // rest of width for (int x=0; x < ww.rem; x++) { DequantizeValue(band, pos, quantParam); pos++; } pos += wr; } } //////////////////////////////////////////////////////////////////// // Decodes and dequantizes HL, and LH band of one level // LH and HH are interleaved in the codestream and must be split // Deccoding and dequantization of HL and LH Band (interleaved) using partitioning scheme // partitions the plane in squares of side length InterBlockSize // It might throw an IOException. void CDecoder::DecodeInterleaved(CWaveletTransform* wtChannel, int level, int quantParam) { CSubband* hlBand = wtChannel->GetSubband(level, HL); CSubband* lhBand = wtChannel->GetSubband(level, LH); const div_t lhH = div(lhBand->GetHeight(), InterBlockSize); const div_t hlW = div(hlBand->GetWidth(), InterBlockSize); const int hlws = hlBand->GetWidth() - InterBlockSize; const int hlwr = hlBand->GetWidth() - hlW.rem; const int lhws = lhBand->GetWidth() - InterBlockSize; const int lhwr = lhBand->GetWidth() - hlW.rem; int hlPos, lhPos; int hlBase = 0, lhBase = 0, hlBase2, lhBase2; ASSERT(lhBand->GetWidth() >= hlBand->GetWidth()); ASSERT(hlBand->GetHeight() >= lhBand->GetHeight()); if (!hlBand->AllocMemory()) ReturnWithError(InsufficientMemory); if (!lhBand->AllocMemory()) ReturnWithError(InsufficientMemory); // correct quantParam with normalization factor quantParam -= level; if (quantParam < 0) quantParam = 0; // main height for (int i=0; i < lhH.quot; i++) { // main width hlBase2 = hlBase; lhBase2 = lhBase; for (int j=0; j < hlW.quot; j++) { hlPos = hlBase2; lhPos = lhBase2; for (int y=0; y < InterBlockSize; y++) { for (int x=0; x < InterBlockSize; x++) { DequantizeValue(hlBand, hlPos, quantParam); DequantizeValue(lhBand, lhPos, quantParam); hlPos++; lhPos++; } hlPos += hlws; lhPos += lhws; } hlBase2 += InterBlockSize; lhBase2 += InterBlockSize; } // rest of width hlPos = hlBase2; lhPos = lhBase2; for (int y=0; y < InterBlockSize; y++) { for (int x=0; x < hlW.rem; x++) { DequantizeValue(hlBand, hlPos, quantParam); DequantizeValue(lhBand, lhPos, quantParam); hlPos++; lhPos++; } // width difference between HL and LH if (lhBand->GetWidth() > hlBand->GetWidth()) { DequantizeValue(lhBand, lhPos, quantParam); } hlPos += hlwr; lhPos += lhwr; hlBase += hlBand->GetWidth(); lhBase += lhBand->GetWidth(); } } // main width hlBase2 = hlBase; lhBase2 = lhBase; for (int j=0; j < hlW.quot; j++) { // rest of height hlPos = hlBase2; lhPos = lhBase2; for (int y=0; y < lhH.rem; y++) { for (int x=0; x < InterBlockSize; x++) { DequantizeValue(hlBand, hlPos, quantParam); DequantizeValue(lhBand, lhPos, quantParam); hlPos++; lhPos++; } hlPos += hlws; lhPos += lhws; } hlBase2 += InterBlockSize; lhBase2 += InterBlockSize; } // rest of height hlPos = hlBase2; lhPos = lhBase2; for (int y=0; y < lhH.rem; y++) { // rest of width for (int x=0; x < hlW.rem; x++) { DequantizeValue(hlBand, hlPos, quantParam); DequantizeValue(lhBand, lhPos, quantParam); hlPos++; lhPos++; } // width difference between HL and LH if (lhBand->GetWidth() > hlBand->GetWidth()) { DequantizeValue(lhBand, lhPos, quantParam); } hlPos += hlwr; lhPos += lhwr; hlBase += hlBand->GetWidth(); } // height difference between HL and LH if (hlBand->GetHeight() > lhBand->GetHeight()) { // total width hlPos = hlBase; for (int j=0; j < hlBand->GetWidth(); j++) { DequantizeValue(hlBand, hlPos, quantParam); hlPos++; } } } //////////////////////////////////////////////////////////////////// /// Skips a given number of bytes in the open stream. /// It might throw an IOException. void CDecoder::Skip(UINT64 offset) { m_stream->SetPos(FSFromCurrent, offset); } ////////////////////////////////////////////////////////////////////// /// Dequantization of a single value at given position in subband. /// If encoded data is available, then stores dequantized band value into /// buffer m_value at position m_valuePos. /// Otherwise reads encoded data block and decodes it. /// It might throw an IOException. /// @param band A subband /// @param bandPos A valid position in subband band /// @param quantParam The quantization parameter void CDecoder::DequantizeValue(CSubband* band, UINT32 bandPos, int quantParam) { ASSERT(m_currentBlock); if (m_currentBlock->IsCompletelyRead()) { // all data of current macro block has been read --> prepare next macro block GetNextMacroBlock(); } band->SetData(bandPos, m_currentBlock->m_value[m_currentBlock->m_valuePos] << quantParam); m_currentBlock->m_valuePos++; } ////////////////////////////////////////////////////////////////////// // Gets next macro block // It might throw an IOException. void CDecoder::GetNextMacroBlock() { // current block has been read --> prepare next current block m_macroBlocksAvailable--; if (m_macroBlocksAvailable > 0) { m_currentBlock = m_macroBlocks[++m_currentBlockIndex]; } else { DecodeBuffer(); } ASSERT(m_currentBlock); } ////////////////////////////////////////////////////////////////////// // Reads next block(s) from stream and decodes them // Decoding scheme: (16 bits) [ ROI ] data // ROI ::= (15 bits) (1 bit) // It might throw an IOException. void CDecoder::DecodeBuffer() { ASSERT(m_macroBlocksAvailable <= 0); // macro block management if (m_macroBlockLen == 1) { ASSERT(m_currentBlock); ReadMacroBlock(m_currentBlock); m_currentBlock->BitplaneDecode(); m_macroBlocksAvailable = 1; } else { m_macroBlocksAvailable = 0; for (int i=0; i < m_macroBlockLen; i++) { // read sequentially several blocks try { ReadMacroBlock(m_macroBlocks[i]); m_macroBlocksAvailable++; } catch(IOException& ex) { if (ex.error == MissingData || ex.error == FormatCannotRead) { break; // no further data available or the data isn't valid PGF data (might occur in streaming or PPPExt) } else { throw; } } } #ifdef LIBPGF_USE_OPENMP // decode in parallel #pragma omp parallel for default(shared) //no declared exceptions in next block #endif for (int i=0; i < m_macroBlocksAvailable; i++) { m_macroBlocks[i]->BitplaneDecode(); } // prepare current macro block m_currentBlockIndex = 0; m_currentBlock = m_macroBlocks[m_currentBlockIndex]; } } ////////////////////////////////////////////////////////////////////// // Reads next block from stream and stores it in the given macro block // It might throw an IOException. void CDecoder::ReadMacroBlock(CMacroBlock* block) { ASSERT(block); UINT16 wordLen; ROIBlockHeader h(BufferSize); int count, expected; #ifdef TRACE //UINT32 filePos = (UINT32)m_stream->GetPos(); //printf("DecodeBuffer: %d\n", filePos); #endif // read wordLen count = expected = sizeof(UINT16); m_stream->Read(&count, &wordLen); if (count != expected) ReturnWithError(MissingData); wordLen = __VAL(wordLen); // convert wordLen if (wordLen > BufferSize) ReturnWithError(FormatCannotRead); #ifdef __PGFROISUPPORT__ // read ROIBlockHeader if (m_roi) { count = expected = sizeof(ROIBlockHeader); m_stream->Read(&count, &h.val); if (count != expected) ReturnWithError(MissingData); h.val = __VAL(h.val); // convert ROIBlockHeader } #endif // save header block->m_header = h; // read data count = expected = wordLen*WordBytes; m_stream->Read(&count, block->m_codeBuffer); if (count != expected) ReturnWithError(MissingData); #ifdef PGF_USE_BIG_ENDIAN // convert data count /= WordBytes; for (int i=0; i < count; i++) { block->m_codeBuffer[i] = __VAL(block->m_codeBuffer[i]); } #endif #ifdef __PGFROISUPPORT__ ASSERT(m_roi && h.rbh.bufferSize <= BufferSize || h.rbh.bufferSize == BufferSize); #else ASSERT(h.rbh.bufferSize == BufferSize); #endif } #ifdef __PGFROISUPPORT__ ////////////////////////////////////////////////////////////////////// // Resets stream position to next tile. // Used with ROI encoding scheme only. // Reads several next blocks from stream but doesn't decode them into macro blocks // Encoding scheme: (16 bits) ROI data // ROI ::= (15 bits) (1 bit) // It might throw an IOException. void CDecoder::SkipTileBuffer() { ASSERT(m_roi); // current macro block belongs to the last tile, so go to the next macro block m_macroBlocksAvailable--; m_currentBlockIndex++; // check if pre-decoded data is available while (m_macroBlocksAvailable > 0 && !m_macroBlocks[m_currentBlockIndex]->m_header.rbh.tileEnd) { m_macroBlocksAvailable--; m_currentBlockIndex++; } if (m_macroBlocksAvailable > 0) { // set new current macro block m_currentBlock = m_macroBlocks[m_currentBlockIndex]; ASSERT(m_currentBlock->m_header.rbh.tileEnd); return; } ASSERT(m_macroBlocksAvailable <= 0); m_macroBlocksAvailable = 0; UINT16 wordLen; ROIBlockHeader h(0); int count, expected; // skips all blocks until tile end do { // read wordLen count = expected = sizeof(wordLen); m_stream->Read(&count, &wordLen); if (count != expected) ReturnWithError(MissingData); wordLen = __VAL(wordLen); // convert wordLen if (wordLen > BufferSize) ReturnWithError(FormatCannotRead); // read ROIBlockHeader count = expected = sizeof(ROIBlockHeader); m_stream->Read(&count, &h.val); if (count != expected) ReturnWithError(MissingData); h.val = __VAL(h.val); // convert ROIBlockHeader // skip data m_stream->SetPos(FSFromCurrent, wordLen*WordBytes); } while (!h.rbh.tileEnd); } #endif ////////////////////////////////////////////////////////////////////// // Decodes macro block into buffer of given size using bit plane coding. // A buffer contains bufferLen UINT32 values, thus, bufferSize bits per bit plane. // Following coding scheme is used: -// Buffer ::= (5 bits) foreach(plane i): Plane[i] +// Buffer ::= (5 bits) foreach (plane i): Plane[i] // Plane[i] ::= [ Sig1 | Sig2 ] [DWORD alignment] refBits // Sig1 ::= 1 (15 bits) codedSigAndSignBits // Sig2 ::= 0 (15 bits) [Sign1 | Sign2 ] [DWORD alignment] sigBits // Sign1 ::= 1 (15 bits) codedSignBits // Sign2 ::= 0 (15 bits) [DWORD alignment] signBits void CDecoder::CMacroBlock::BitplaneDecode() { UINT32 bufferSize = m_header.rbh.bufferSize; ASSERT(bufferSize <= BufferSize); // clear significance vector for (UINT32 k=0; k < bufferSize; k++) { m_sigFlagVector[k] = false; } m_sigFlagVector[bufferSize] = true; // sentinel // clear output buffer for (UINT32 k=0; k < BufferSize; k++) { m_value[k] = 0; } // read number of bit planes // UINT32 nPlanes = GetValueBlock(m_codeBuffer, 0, MaxBitPlanesLog); UINT32 codePos = MaxBitPlanesLog; // loop through all bit planes if (nPlanes == 0) nPlanes = MaxBitPlanes + 1; ASSERT(0 < nPlanes && nPlanes <= MaxBitPlanes + 1); DataT planeMask = 1 << (nPlanes - 1); for (int plane = nPlanes - 1; plane >= 0; plane--) { UINT32 sigLen = 0; // read RL code if (GetBit(m_codeBuffer, codePos)) { // RL coding of sigBits is used // <1>_ codePos++; // read codeLen UINT32 codeLen = GetValueBlock(m_codeBuffer, codePos, RLblockSizeLen); ASSERT(codeLen <= MaxCodeLen); // position of encoded sigBits and signBits UINT32 sigPos = codePos + RLblockSizeLen; ASSERT(sigPos < CodeBufferBitLen); // refinement bits codePos = AlignWordPos(sigPos + codeLen); ASSERT(codePos < CodeBufferBitLen); // run-length decode significant bits and signs from m_codeBuffer and // read refinement bits from m_codeBuffer and compose bit plane sigLen = ComposeBitplaneRLD(bufferSize, planeMask, sigPos, &m_codeBuffer[codePos >> WordWidthLog]); } else { // no RL coding is used for sigBits and signBits together // <0> codePos++; // read sigLen sigLen = GetValueBlock(m_codeBuffer, codePos, RLblockSizeLen); ASSERT(sigLen <= MaxCodeLen); codePos += RLblockSizeLen; ASSERT(codePos < CodeBufferBitLen); // read RL code for signBits if (GetBit(m_codeBuffer, codePos)) { // RL coding is used just for signBits // <1>__ codePos++; // read codeLen UINT32 codeLen = GetValueBlock(m_codeBuffer, codePos, RLblockSizeLen); ASSERT(codeLen <= MaxCodeLen); // sign bits UINT32 signPos = codePos + RLblockSizeLen; ASSERT(signPos < CodeBufferBitLen); // significant bits UINT32 sigPos = AlignWordPos(signPos + codeLen); ASSERT(sigPos < CodeBufferBitLen); // refinement bits codePos = AlignWordPos(sigPos + sigLen); ASSERT(codePos < CodeBufferBitLen); // read significant and refinement bitset from m_codeBuffer sigLen = ComposeBitplaneRLD(bufferSize, planeMask, &m_codeBuffer[sigPos >> WordWidthLog], &m_codeBuffer[codePos >> WordWidthLog], signPos); } else { // RL coding of signBits was not efficient and therefore not used // <0>___ codePos++; // read signLen UINT32 signLen = GetValueBlock(m_codeBuffer, codePos, RLblockSizeLen); ASSERT(signLen <= MaxCodeLen); // sign bits UINT32 signPos = AlignWordPos(codePos + RLblockSizeLen); ASSERT(signPos < CodeBufferBitLen); // significant bits UINT32 sigPos = AlignWordPos(signPos + signLen); ASSERT(sigPos < CodeBufferBitLen); // refinement bits codePos = AlignWordPos(sigPos + sigLen); ASSERT(codePos < CodeBufferBitLen); // read significant and refinement bitset from m_codeBuffer sigLen = ComposeBitplane(bufferSize, planeMask, &m_codeBuffer[sigPos >> WordWidthLog], &m_codeBuffer[codePos >> WordWidthLog], &m_codeBuffer[signPos >> WordWidthLog]); } } // start of next chunk codePos = AlignWordPos(codePos + bufferSize - sigLen); ASSERT(codePos < CodeBufferBitLen); // next plane planeMask >>= 1; } m_valuePos = 0; } //////////////////////////////////////////////////////////////////// // Reconstructs bitplane from significant bitset and refinement bitset // returns length [bits] of sigBits // input: sigBits, refBits, signBits // output: m_value UINT32 CDecoder::CMacroBlock::ComposeBitplane(UINT32 bufferSize, DataT planeMask, UINT32* sigBits, UINT32* refBits, UINT32* signBits) { ASSERT(sigBits); ASSERT(refBits); ASSERT(signBits); UINT32 valPos = 0, signPos = 0, refPos = 0, sigPos = 0; while (valPos < bufferSize) { // search next 1 in m_sigFlagVector using searching with sentinel UINT32 sigEnd = valPos; while(!m_sigFlagVector[sigEnd]) { sigEnd++; } sigEnd -= valPos; sigEnd += sigPos; // search 1's in sigBits[sigPos..sigEnd) // these 1's are significant bits while (sigPos < sigEnd) { // search 0's UINT32 zerocnt = SeekBitRange(sigBits, sigPos, sigEnd - sigPos); sigPos += zerocnt; valPos += zerocnt; if (sigPos < sigEnd) { // write bit to m_value SetBitAtPos(valPos, planeMask); // copy sign bit SetSign(valPos, GetBit(signBits, signPos++)); // update significance flag vector m_sigFlagVector[valPos++] = true; sigPos++; } } // refinement bit if (valPos < bufferSize) { // write one refinement bit if (GetBit(refBits, refPos)) { SetBitAtPos(valPos, planeMask); } refPos++; valPos++; } } ASSERT(sigPos <= bufferSize); ASSERT(refPos <= bufferSize); ASSERT(signPos <= bufferSize); ASSERT(valPos == bufferSize); return sigPos; } //////////////////////////////////////////////////////////////////// // Reconstructs bitplane from significant bitset and refinement bitset // returns length [bits] of decoded significant bits // input: RL encoded sigBits and signBits in m_codeBuffer, refBits // output: m_value // RLE: // - Decode run of 2^k zeros by a single 0. // - Decode run of count 0's followed by a 1 with codeword: 1x // - x is 0: if a positive sign has been stored, otherwise 1 // - Read each bit from m_codeBuffer[codePos] and increment codePos. UINT32 CDecoder::CMacroBlock::ComposeBitplaneRLD(UINT32 bufferSize, DataT planeMask, UINT32 codePos, UINT32* refBits) { ASSERT(refBits); UINT32 valPos = 0, refPos = 0; UINT32 sigPos = 0, sigEnd; UINT32 k = 3; UINT32 runlen = 1 << k; // = 2^k UINT32 count = 0, rest = 0; bool set1 = false; while (valPos < bufferSize) { // search next 1 in m_sigFlagVector using searching with sentinel sigEnd = valPos; while(!m_sigFlagVector[sigEnd]) { sigEnd++; } sigEnd -= valPos; sigEnd += sigPos; while (sigPos < sigEnd) { if (rest || set1) { // rest of last run sigPos += rest; valPos += rest; rest = 0; } else { // decode significant bits if (GetBit(m_codeBuffer, codePos++)) { // extract counter and generate zero run of length count if (k > 0) { // extract counter count = GetValueBlock(m_codeBuffer, codePos, k); codePos += k; if (count > 0) { sigPos += count; valPos += count; } // adapt k (half run-length interval) k--; runlen >>= 1; } set1 = true; } else { // generate zero run of length 2^k sigPos += runlen; valPos += runlen; // adapt k (double run-length interval) if (k < WordWidth) { k++; runlen <<= 1; } } } if (sigPos < sigEnd) { if (set1) { set1 = false; // write 1 bit SetBitAtPos(valPos, planeMask); // set sign bit SetSign(valPos, GetBit(m_codeBuffer, codePos++)); // update significance flag vector m_sigFlagVector[valPos++] = true; sigPos++; } } else { rest = sigPos - sigEnd; sigPos = sigEnd; valPos -= rest; } } // refinement bit if (valPos < bufferSize) { // write one refinement bit if (GetBit(refBits, refPos)) { SetBitAtPos(valPos, planeMask); } refPos++; valPos++; } } ASSERT(sigPos <= bufferSize); ASSERT(refPos <= bufferSize); ASSERT(valPos == bufferSize); return sigPos; } //////////////////////////////////////////////////////////////////// // Reconstructs bitplane from significant bitset, refinement bitset, and RL encoded sign bits // returns length [bits] of sigBits // input: sigBits, refBits, RL encoded signBits // output: m_value // RLE: // decode run of 2^k 1's by a single 1 // decode run of count 1's followed by a 0 with codeword: 0 UINT32 CDecoder::CMacroBlock::ComposeBitplaneRLD(UINT32 bufferSize, DataT planeMask, UINT32* sigBits, UINT32* refBits, UINT32 signPos) { ASSERT(sigBits); ASSERT(refBits); UINT32 valPos = 0, refPos = 0; UINT32 sigPos = 0, sigEnd; UINT32 zerocnt, count = 0; UINT32 k = 0; UINT32 runlen = 1 << k; // = 2^k bool signBit = false; bool zeroAfterRun = false; while (valPos < bufferSize) { // search next 1 in m_sigFlagVector using searching with sentinel sigEnd = valPos; while(!m_sigFlagVector[sigEnd]) { sigEnd++; } sigEnd -= valPos; sigEnd += sigPos; // search 1's in sigBits[sigPos..sigEnd) // these 1's are significant bits while (sigPos < sigEnd) { // search 0's zerocnt = SeekBitRange(sigBits, sigPos, sigEnd - sigPos); sigPos += zerocnt; valPos += zerocnt; if (sigPos < sigEnd) { // write bit to m_value SetBitAtPos(valPos, planeMask); // check sign bit if (count == 0) { // all 1's have been set if (zeroAfterRun) { // finish the run with a 0 signBit = false; zeroAfterRun = false; } else { // decode next sign bit if (GetBit(m_codeBuffer, signPos++)) { // generate 1's run of length 2^k count = runlen - 1; signBit = true; // adapt k (double run-length interval) if (k < WordWidth) { k++; runlen <<= 1; } } else { // extract counter and generate 1's run of length count if (k > 0) { // extract counter count = GetValueBlock(m_codeBuffer, signPos, k); signPos += k; // adapt k (half run-length interval) k--; runlen >>= 1; } if (count > 0) { count--; signBit = true; zeroAfterRun = true; } else { signBit = false; } } } } else { ASSERT(count > 0); ASSERT(signBit); count--; } // copy sign bit SetSign(valPos, signBit); // update significance flag vector m_sigFlagVector[valPos++] = true; sigPos++; } } // refinement bit if (valPos < bufferSize) { // write one refinement bit if (GetBit(refBits, refPos)) { SetBitAtPos(valPos, planeMask); } refPos++; valPos++; } } ASSERT(sigPos <= bufferSize); ASSERT(refPos <= bufferSize); ASSERT(valPos == bufferSize); return sigPos; } //////////////////////////////////////////////////////////////////// #ifdef TRACE void CDecoder::DumpBuffer() { //printf("\nDump\n"); //for (int i=0; i < BufferSize; i++) { // printf("%d", m_value[i]); //} } #if defined(__GNUC__) #pragma GCC diagnostic pop #endif #if defined(__APPLE__) #pragma clang diagnostic pop #endif #endif //TRACE diff --git a/core/libs/pgfutils/libpgf/Encoder.cpp b/core/libs/pgfutils/libpgf/Encoder.cpp index a30c482ec0..aaa04ee46c 100644 --- a/core/libs/pgfutils/libpgf/Encoder.cpp +++ b/core/libs/pgfutils/libpgf/Encoder.cpp @@ -1,830 +1,830 @@ /* * The Progressive Graphics File; http://www.libpgf.org * * $Date: 2007-02-03 13:04:21 +0100 (Sa, 03 Feb 2007) $ * $Revision: 280 $ * * This file Copyright (C) 2006 xeraina GmbH, Switzerland * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE * as published by the Free Software Foundation; either version 2.1 * 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ ////////////////////////////////////////////////////////////////////// /// @file Encoder.cpp /// @brief PGF encoder class implementation /// @author C. Stamm, R. Spuler #include "Encoder.h" #ifdef TRACE #include #endif ////////////////////////////////////////////////////// // PGF: file structure // // PGFPreHeader PGFHeader [PGFPostHeader] LevelLengths Level_n-1 Level_n-2 ... Level_0 // PGFPostHeader ::= [ColorTable] [UserData] // LevelLengths ::= UINT32[nLevels] ////////////////////////////////////////////////////// // Encoding scheme // input: wavelet coefficients stored in subbands // output: binary file // // subband // | // m_value [BufferSize] // | | | // m_sign sigBits refBits [BufferSize, BufferLen, BufferLen] // | | | // m_codeBuffer (for each plane: RLcodeLength (16 bit), RLcoded sigBits + m_sign, refBits) // | // file (for each buffer: packedLength (16 bit), packed bits) // // Constants #define CodeBufferBitLen (CodeBufferLen*WordWidth) ///< max number of bits in m_codeBuffer #define MaxCodeLen ((1 << RLblockSizeLen) - 1) ///< max length of RL encoded block ////////////////////////////////////////////////////// /// Write pre-header, header, postHeader, and levelLength. /// It might throw an IOException. /// @param stream A PGF stream /// @param preHeader A already filled in PGF pre-header /// @param header An already filled in PGF header /// @param postHeader [in] An already filled in PGF post-header (containing color table, user data, ...) /// @param userDataPos [out] File position of user data /// @param useOMP If true, then the encoder will use multi-threading based on openMP CEncoder::CEncoder(CPGFStream* stream, PGFPreHeader preHeader, PGFHeader header, const PGFPostHeader& postHeader, UINT64& userDataPos, bool useOMP) : m_stream(stream) , m_bufferStartPos(0) , m_currLevelIndex(0) , m_nLevels(header.nLevels) , m_favorSpeed(false) , m_forceWriting(false) #ifdef __PGFROISUPPORT__ , m_roi(false) #endif { ASSERT(m_stream); int count; m_lastMacroBlock = 0; m_levelLength = nullptr; // set number of threads #ifdef LIBPGF_USE_OPENMP m_macroBlockLen = omp_get_num_procs(); #else m_macroBlockLen = 1; #endif if (useOMP && m_macroBlockLen > 1) { #ifdef LIBPGF_USE_OPENMP omp_set_num_threads(m_macroBlockLen); #endif // create macro block array m_macroBlocks = new(std::nothrow) CMacroBlock*[m_macroBlockLen]; if (!m_macroBlocks) ReturnWithError(InsufficientMemory); for (int i=0; i < m_macroBlockLen; i++) m_macroBlocks[i] = new CMacroBlock(this); m_currentBlock = m_macroBlocks[m_lastMacroBlock++]; } else { m_macroBlocks = 0; m_macroBlockLen = 1; m_currentBlock = new CMacroBlock(this); } // save file position m_startPosition = m_stream->GetPos(); // write preHeader preHeader.hSize = __VAL(preHeader.hSize); count = PreHeaderSize; m_stream->Write(&count, &preHeader); // write file header header.height = __VAL(header.height); header.width = __VAL(header.width); count = HeaderSize; m_stream->Write(&count, &header); // write postHeader if (header.mode == ImageModeIndexedColor) { // write color table count = ColorTableSize; m_stream->Write(&count, (void *)postHeader.clut); } // save user data file position userDataPos = m_stream->GetPos(); if (postHeader.userDataLen) { if (postHeader.userData) { // write user data count = postHeader.userDataLen; m_stream->Write(&count, postHeader.userData); } else { m_stream->SetPos(FSFromCurrent, count); } } // save level length file position m_levelLengthPos = m_stream->GetPos(); } ////////////////////////////////////////////////////// // Destructor CEncoder::~CEncoder() { if (m_macroBlocks) { for (int i=0; i < m_macroBlockLen; i++) delete m_macroBlocks[i]; delete[] m_macroBlocks; } else { delete m_currentBlock; } } ///////////////////////////////////////////////////////////////////// /// Increase post-header size and write new size into stream. /// @param preHeader An already filled in PGF pre-header /// It might throw an IOException. void CEncoder::UpdatePostHeaderSize(PGFPreHeader preHeader) { UINT64 curPos = m_stream->GetPos(); // end of user data int count = PreHeaderSize; // write preHeader SetStreamPosToStart(); preHeader.hSize = __VAL(preHeader.hSize); m_stream->Write(&count, &preHeader); m_stream->SetPos(FSFromStart, curPos); } ///////////////////////////////////////////////////////////////////// /// Create level length data structure and write a place holder into stream. /// It might throw an IOException. /// @param levelLength A reference to an integer array, large enough to save the relative file positions of all PGF levels /// @return number of bytes written into stream UINT32 CEncoder::WriteLevelLength(UINT32*& levelLength) { // renew levelLength delete[] levelLength; levelLength = new(std::nothrow) UINT32[m_nLevels]; if (!levelLength) ReturnWithError(InsufficientMemory); for (UINT8 l = 0; l < m_nLevels; l++) levelLength[l] = 0; m_levelLength = levelLength; // save level length file position m_levelLengthPos = m_stream->GetPos(); // write dummy levelLength int count = m_nLevels*WordBytes; m_stream->Write(&count, m_levelLength); // save current file position SetBufferStartPos(); return count; } ////////////////////////////////////////////////////// /// Write new levelLength into stream. /// It might throw an IOException. /// @return Written image bytes. UINT32 CEncoder::UpdateLevelLength() { UINT64 curPos = m_stream->GetPos(); // end of image // set file pos to levelLength m_stream->SetPos(FSFromStart, m_levelLengthPos); if (m_levelLength) { #ifdef PGF_USE_BIG_ENDIAN UINT32 levelLength; int count = WordBytes; for (int i=0; i < m_currLevelIndex; i++) { levelLength = __VAL(UINT32(m_levelLength[i])); m_stream->Write(&count, &levelLength); } #else int count = m_currLevelIndex*WordBytes; m_stream->Write(&count, m_levelLength); #endif //PGF_USE_BIG_ENDIAN } else { int count = m_currLevelIndex*WordBytes; m_stream->SetPos(FSFromCurrent, count); } // begin of image UINT32 retValue = UINT32(curPos - m_stream->GetPos()); // restore file position m_stream->SetPos(FSFromStart, curPos); return retValue; } ///////////////////////////////////////////////////////////////////// /// Partitions a rectangular region of a given subband. /// Partitioning scheme: The plane is partitioned in squares of side length LinBlockSize. /// Write wavelet coefficients from subband into the input buffer of a macro block. /// It might throw an IOException. /// @param band A subband /// @param width The width of the rectangle /// @param height The height of the rectangle /// @param startPos The absolute subband position of the top left corner of the rectangular region /// @param pitch The number of bytes in row of the subband void CEncoder::Partition(CSubband* band, int width, int height, int startPos, int pitch) { ASSERT(band); const div_t hh = div(height, LinBlockSize); const div_t ww = div(width, LinBlockSize); const int ws = pitch - LinBlockSize; const int wr = pitch - ww.rem; int pos, base = startPos, base2; // main height for (int i=0; i < hh.quot; i++) { // main width base2 = base; for (int j=0; j < ww.quot; j++) { pos = base2; for (int y=0; y < LinBlockSize; y++) { for (int x=0; x < LinBlockSize; x++) { WriteValue(band, pos); pos++; } pos += ws; } base2 += LinBlockSize; } // rest of width pos = base2; for (int y=0; y < LinBlockSize; y++) { for (int x=0; x < ww.rem; x++) { WriteValue(band, pos); pos++; } pos += wr; base += pitch; } } // main width base2 = base; for (int j=0; j < ww.quot; j++) { // rest of height pos = base2; for (int y=0; y < hh.rem; y++) { for (int x=0; x < LinBlockSize; x++) { WriteValue(band, pos); pos++; } pos += ws; } base2 += LinBlockSize; } // rest of height pos = base2; for (int y=0; y < hh.rem; y++) { // rest of width for (int x=0; x < ww.rem; x++) { WriteValue(band, pos); pos++; } pos += wr; } } ////////////////////////////////////////////////////// /// Pad buffer with zeros and encode buffer. /// It might throw an IOException. void CEncoder::Flush() { if (m_currentBlock->m_valuePos > 0) { // pad buffer with zeros memset(&(m_currentBlock->m_value[m_currentBlock->m_valuePos]), 0, (BufferSize - m_currentBlock->m_valuePos)*DataTSize); m_currentBlock->m_valuePos = BufferSize; // encode buffer m_forceWriting = true; // makes sure that the following EncodeBuffer is really written into the stream EncodeBuffer(ROIBlockHeader(m_currentBlock->m_valuePos, true)); } } ///////////////////////////////////////////////////////////////////// // Stores band value from given position bandPos into buffer m_value at position m_valuePos // If buffer is full encode it to file // It might throw an IOException. void CEncoder::WriteValue(CSubband* band, int bandPos) { if (m_currentBlock->m_valuePos == BufferSize) { EncodeBuffer(ROIBlockHeader(BufferSize, false)); } DataT val = m_currentBlock->m_value[m_currentBlock->m_valuePos++] = band->GetData(bandPos); UINT32 v = abs(val); if (v > m_currentBlock->m_maxAbsValue) m_currentBlock->m_maxAbsValue = v; } ///////////////////////////////////////////////////////////////////// // Encode buffer and write data into stream. // h contains buffer size and flag indicating end of tile. // Encoding scheme: (16 bits) [ ROI ] data // ROI ::= (15 bits) (1 bit) // It might throw an IOException. void CEncoder::EncodeBuffer(ROIBlockHeader h) { ASSERT(m_currentBlock); #ifdef __PGFROISUPPORT__ ASSERT(m_roi && h.rbh.bufferSize <= BufferSize || h.rbh.bufferSize == BufferSize); #else ASSERT(h.rbh.bufferSize == BufferSize); #endif m_currentBlock->m_header = h; // macro block management if (m_macroBlockLen == 1) { m_currentBlock->BitplaneEncode(); WriteMacroBlock(m_currentBlock); } else { // save last level index int lastLevelIndex = m_currentBlock->m_lastLevelIndex; if (m_forceWriting || m_lastMacroBlock == m_macroBlockLen) { // encode macro blocks /* volatile OSError error = NoError; #ifdef LIBPGF_USE_OPENMP #pragma omp parallel for ordered default(shared) #endif for (int i=0; i < m_lastMacroBlock; i++) { if (error == NoError) { m_macroBlocks[i]->BitplaneEncode(); #ifdef LIBPGF_USE_OPENMP #pragma omp ordered #endif { try { WriteMacroBlock(m_macroBlocks[i]); } catch (IOException& e) { error = e.error; } delete m_macroBlocks[i]; m_macroBlocks[i] = 0; } } } if (error != NoError) ReturnWithError(error); */ #ifdef LIBPGF_USE_OPENMP #pragma omp parallel for default(shared) //no declared exceptions in next block #endif for (int i=0; i < m_lastMacroBlock; i++) { m_macroBlocks[i]->BitplaneEncode(); } for (int i=0; i < m_lastMacroBlock; i++) { WriteMacroBlock(m_macroBlocks[i]); } // prepare for next round m_forceWriting = false; m_lastMacroBlock = 0; } // re-initialize macro block m_currentBlock = m_macroBlocks[m_lastMacroBlock++]; m_currentBlock->Init(lastLevelIndex); } } ///////////////////////////////////////////////////////////////////// // Write encoded macro block into stream. // It might throw an IOException. void CEncoder::WriteMacroBlock(CMacroBlock* block) { ASSERT(block); #ifdef __PGFROISUPPORT__ ROIBlockHeader h = block->m_header; #endif UINT16 wordLen = UINT16(NumberOfWords(block->m_codePos)); ASSERT(wordLen <= CodeBufferLen); int count = sizeof(UINT16); #ifdef TRACE //UINT32 filePos = (UINT32)m_stream->GetPos(); //printf("EncodeBuffer: %d\n", filePos); #endif #ifdef PGF_USE_BIG_ENDIAN // write wordLen UINT16 wl = __VAL(wordLen); m_stream->Write(&count, &wl); ASSERT(count == sizeof(UINT16)); #ifdef __PGFROISUPPORT__ // write ROIBlockHeader if (m_roi) { count = sizeof(ROIBlockHeader); h.val = __VAL(h.val); m_stream->Write(&count, &h.val); ASSERT(count == sizeof(ROIBlockHeader)); } #endif // __PGFROISUPPORT__ // convert data for (int i=0; i < wordLen; i++) { block->m_codeBuffer[i] = __VAL(block->m_codeBuffer[i]); } #else // write wordLen m_stream->Write(&count, &wordLen); ASSERT(count == sizeof(UINT16)); #ifdef __PGFROISUPPORT__ // write ROIBlockHeader if (m_roi) { count = sizeof(ROIBlockHeader); m_stream->Write(&count, &h.val); ASSERT(count == sizeof(ROIBlockHeader)); } #endif // __PGFROISUPPORT__ #endif // PGF_USE_BIG_ENDIAN // write encoded data into stream count = wordLen*WordBytes; m_stream->Write(&count, block->m_codeBuffer); // store levelLength if (m_levelLength) { // store level length // EncodeBuffer has been called after m_lastLevelIndex has been updated ASSERT(m_currLevelIndex < m_nLevels); m_levelLength[m_currLevelIndex] += (UINT32)ComputeBufferLength(); m_currLevelIndex = block->m_lastLevelIndex + 1; } // prepare for next buffer SetBufferStartPos(); // reset values block->m_valuePos = 0; block->m_maxAbsValue = 0; } //////////////////////////////////////////////////////// // Encode buffer of given size using bit plane coding. // A buffer contains bufferLen UINT32 values, thus, bufferSize bits per bit plane. // Following coding scheme is used: -// Buffer ::= (5 bits) foreach(plane i): Plane[i] +// Buffer ::= (5 bits) foreach (plane i): Plane[i] // Plane[i] ::= [ Sig1 | Sig2 ] [DWORD alignment] refBits // Sig1 ::= 1 (15 bits) codedSigAndSignBits // Sig2 ::= 0 (15 bits) [Sign1 | Sign2 ] [DWORD alignment] sigBits // Sign1 ::= 1 (15 bits) codedSignBits // Sign2 ::= 0 (15 bits) [DWORD alignment] signBits void CEncoder::CMacroBlock::BitplaneEncode() { UINT8 nPlanes; UINT32 sigLen, codeLen = 0, wordPos, refLen, signLen; UINT32 sigBits[BufferLen] = { 0 }; UINT32 refBits[BufferLen] = { 0 }; UINT32 signBits[BufferLen] = { 0 }; UINT32 planeMask; UINT32 bufferSize = m_header.rbh.bufferSize; ASSERT(bufferSize <= BufferSize); bool useRL; #ifdef TRACE //printf("which thread: %d\n", omp_get_thread_num()); #endif // clear significance vector for (UINT32 k=0; k < bufferSize; k++) { m_sigFlagVector[k] = false; } m_sigFlagVector[bufferSize] = true; // sentinel // clear output buffer for (UINT32 k=0; k < bufferSize; k++) { m_codeBuffer[k] = 0; } m_codePos = 0; // compute number of bit planes and split buffer into separate bit planes nPlanes = NumberOfBitplanes(); // write number of bit planes to m_codeBuffer // SetValueBlock(m_codeBuffer, 0, nPlanes, MaxBitPlanesLog); m_codePos += MaxBitPlanesLog; // loop through all bit planes if (nPlanes == 0) nPlanes = MaxBitPlanes + 1; planeMask = 1 << (nPlanes - 1); for (int plane = nPlanes - 1; plane >= 0; plane--) { // clear significant bitset for (UINT32 k=0; k < BufferLen; k++) { sigBits[k] = 0; } // split bitplane in significant bitset and refinement bitset sigLen = DecomposeBitplane(bufferSize, planeMask, m_codePos + RLblockSizeLen + 1, sigBits, refBits, signBits, signLen, codeLen); if (sigLen > 0 && codeLen <= MaxCodeLen && codeLen < AlignWordPos(sigLen) + AlignWordPos(signLen) + 2*RLblockSizeLen) { // set RL code bit // <1> SetBit(m_codeBuffer, m_codePos++); // write length codeLen to m_codeBuffer SetValueBlock(m_codeBuffer, m_codePos, codeLen, RLblockSizeLen); m_codePos += RLblockSizeLen + codeLen; } else { #ifdef TRACE //printf("new\n"); //for (UINT32 i=0; i < bufferSize; i++) { // printf("%s", (GetBit(sigBits, i)) ? "1" : "_"); // if (i%120 == 119) printf("\n"); //} //printf("\n"); #endif // TRACE // run-length coding wasn't efficient enough // we don't use RL coding for sigBits // <0> ClearBit(m_codeBuffer, m_codePos++); // write length sigLen to m_codeBuffer ASSERT(sigLen <= MaxCodeLen); SetValueBlock(m_codeBuffer, m_codePos, sigLen, RLblockSizeLen); m_codePos += RLblockSizeLen; if (m_encoder->m_favorSpeed || signLen == 0) { useRL = false; } else { // overwrite m_codeBuffer useRL = true; // run-length encode m_sign and append them to the m_codeBuffer codeLen = RLESigns(m_codePos + RLblockSizeLen + 1, signBits, signLen); } if (useRL && codeLen <= MaxCodeLen && codeLen < signLen) { // RL encoding of m_sign was efficient // <1>_ // write RL code bit SetBit(m_codeBuffer, m_codePos++); // write codeLen to m_codeBuffer SetValueBlock(m_codeBuffer, m_codePos, codeLen, RLblockSizeLen); // compute position of sigBits wordPos = NumberOfWords(m_codePos + RLblockSizeLen + codeLen); ASSERT(0 <= wordPos && wordPos < CodeBufferLen); } else { // RL encoding of signBits wasn't efficient // <0>__ // clear RL code bit ClearBit(m_codeBuffer, m_codePos++); // write signLen to m_codeBuffer ASSERT(signLen <= MaxCodeLen); SetValueBlock(m_codeBuffer, m_codePos, signLen, RLblockSizeLen); // write signBits to m_codeBuffer wordPos = NumberOfWords(m_codePos + RLblockSizeLen); ASSERT(0 <= wordPos && wordPos < CodeBufferLen); codeLen = NumberOfWords(signLen); for (UINT32 k=0; k < codeLen; k++) { m_codeBuffer[wordPos++] = signBits[k]; } } // write sigBits // _ ASSERT(0 <= wordPos && wordPos < CodeBufferLen); refLen = NumberOfWords(sigLen); for (UINT32 k=0; k < refLen; k++) { m_codeBuffer[wordPos++] = sigBits[k]; } m_codePos = wordPos << WordWidthLog; } // append refinement bitset (aligned to word boundary) // _ wordPos = NumberOfWords(m_codePos); ASSERT(0 <= wordPos && wordPos < CodeBufferLen); refLen = NumberOfWords(bufferSize - sigLen); for (UINT32 k=0; k < refLen; k++) { m_codeBuffer[wordPos++] = refBits[k]; } m_codePos = wordPos << WordWidthLog; planeMask >>= 1; } ASSERT(0 <= m_codePos && m_codePos <= CodeBufferBitLen); } ////////////////////////////////////////////////////////// // Split bitplane of length bufferSize into significant and refinement bitset // returns length [bits] of significant bits // input: bufferSize, planeMask, codePos // output: sigBits, refBits, signBits, signLen [bits], codeLen [bits] // RLE // - Encode run of 2^k zeros by a single 0. // - Encode run of count 0's followed by a 1 with codeword: 1x // - x is 0: if a positive sign is stored, otherwise 1 // - Store each bit in m_codeBuffer[codePos] and increment codePos. UINT32 CEncoder::CMacroBlock::DecomposeBitplane(UINT32 bufferSize, UINT32 planeMask, UINT32 codePos, UINT32* sigBits, UINT32* refBits, UINT32* signBits, UINT32& signLen, UINT32& codeLen) { ASSERT(sigBits); ASSERT(refBits); ASSERT(signBits); ASSERT(codePos < CodeBufferBitLen); UINT32 sigPos = 0; UINT32 valuePos = 0, valueEnd; UINT32 refPos = 0; // set output value signLen = 0; // prepare RLE of Sigs and Signs const UINT32 outStartPos = codePos; UINT32 k = 3; UINT32 runlen = 1 << k; // = 2^k UINT32 count = 0; while (valuePos < bufferSize) { // search next 1 in m_sigFlagVector using searching with sentinel valueEnd = valuePos; while(!m_sigFlagVector[valueEnd]) { valueEnd++; } // search 1's in m_value[plane][valuePos..valueEnd) // these 1's are significant bits while (valuePos < valueEnd) { if (GetBitAtPos(valuePos, planeMask)) { // RLE encoding // encode run of count 0's followed by a 1 // with codeword: 1(signBits[signPos]) SetBit(m_codeBuffer, codePos++); if (k > 0) { SetValueBlock(m_codeBuffer, codePos, count, k); codePos += k; // adapt k (half the zero run-length) k--; runlen >>= 1; } // copy and write sign bit if (m_value[valuePos] < 0) { SetBit(signBits, signLen++); SetBit(m_codeBuffer, codePos++); } else { ClearBit(signBits, signLen++); ClearBit(m_codeBuffer, codePos++); } // write a 1 to sigBits SetBit(sigBits, sigPos++); // update m_sigFlagVector m_sigFlagVector[valuePos] = true; // prepare for next run count = 0; } else { // RLE encoding count++; if (count == runlen) { // encode run of 2^k zeros by a single 0 ClearBit(m_codeBuffer, codePos++); // adapt k (double the zero run-length) if (k < WordWidth) { k++; runlen <<= 1; } // prepare for next run count = 0; } // write 0 to sigBits sigPos++; } valuePos++; } // refinement bit if (valuePos < bufferSize) { // write one refinement bit if (GetBitAtPos(valuePos++, planeMask)) { SetBit(refBits, refPos); } else { ClearBit(refBits, refPos); } refPos++; } } // RLE encoding of the rest of the plane // encode run of count 0's followed by a 1 // with codeword: 1(signBits[signPos]) SetBit(m_codeBuffer, codePos++); if (k > 0) { SetValueBlock(m_codeBuffer, codePos, count, k); codePos += k; } // write dmmy sign bit SetBit(m_codeBuffer, codePos++); // write word filler zeros ASSERT(sigPos <= bufferSize); ASSERT(refPos <= bufferSize); ASSERT(signLen <= bufferSize); ASSERT(valuePos == bufferSize); ASSERT(codePos >= outStartPos && codePos < CodeBufferBitLen); codeLen = codePos - outStartPos; return sigPos; } /////////////////////////////////////////////////////// // Compute number of bit planes needed UINT8 CEncoder::CMacroBlock::NumberOfBitplanes() { UINT8 cnt = 0; // determine number of bitplanes for max value if (m_maxAbsValue > 0) { while (m_maxAbsValue > 0) { m_maxAbsValue >>= 1; cnt++; } if (cnt == MaxBitPlanes + 1) cnt = 0; // end cs ASSERT(cnt <= MaxBitPlanes); ASSERT((cnt >> MaxBitPlanesLog) == 0); return cnt; } else { return 1; } } ////////////////////////////////////////////////////// // Adaptive Run-Length encoder for long sequences of ones. // Returns length of output in bits. // - Encode run of 2^k ones by a single 1. // - Encode run of count 1's followed by a 0 with codeword: 0. // - Store each bit in m_codeBuffer[codePos] and increment codePos. UINT32 CEncoder::CMacroBlock::RLESigns(UINT32 codePos, UINT32* signBits, UINT32 signLen) { ASSERT(signBits); ASSERT(0 <= codePos && codePos < CodeBufferBitLen); ASSERT(0 < signLen && signLen <= BufferSize); const UINT32 outStartPos = codePos; UINT32 k = 0; UINT32 runlen = 1 << k; // = 2^k UINT32 count = 0; UINT32 signPos = 0; while (signPos < signLen) { // search next 0 in signBits starting at position signPos count = SeekBit1Range(signBits, signPos, __min(runlen, signLen - signPos)); // count 1's found if (count == runlen) { // encode run of 2^k ones by a single 1 signPos += count; SetBit(m_codeBuffer, codePos++); // adapt k (double the 1's run-length) if (k < WordWidth) { k++; runlen <<= 1; } } else { // encode run of count 1's followed by a 0 // with codeword: 0(count) signPos += count + 1; ClearBit(m_codeBuffer, codePos++); if (k > 0) { SetValueBlock(m_codeBuffer, codePos, count, k); codePos += k; } // adapt k (half the 1's run-length) if (k > 0) { k--; runlen >>= 1; } } } ASSERT(signPos == signLen || signPos == signLen + 1); ASSERT(codePos >= outStartPos && codePos < CodeBufferBitLen); return codePos - outStartPos; } ////////////////////////////////////////////////////// #ifdef TRACE void CEncoder::DumpBuffer() const { //printf("\nDump\n"); //for (UINT32 i=0; i < BufferSize; i++) { // printf("%d", m_value[i]); //} //printf("\n"); } #endif //TRACE diff --git a/core/libs/properties/itempropertiessidebardb.cpp b/core/libs/properties/itempropertiessidebardb.cpp index 35b5b4628b..4869fe0109 100644 --- a/core/libs/properties/itempropertiessidebardb.cpp +++ b/core/libs/properties/itempropertiessidebardb.cpp @@ -1,705 +1,705 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-11-17 * Description : item properties side bar using data from digiKam database. * * Copyright (C) 2004-2019 by Gilles Caulier * Copyright (C) 2007-2012 by Marcel Wiesweg * Copyright (C) 2010-2011 by Martin Klapetek * Copyright (C) 2011 by Michael G. Hansen * * 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, 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. * * ============================================================ */ #include "itempropertiessidebardb.h" // Qt includes #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "applicationsettings.h" #include "coredbinfocontainers.h" #include "coredbwatch.h" #include "dimg.h" #include "itemattributeswatch.h" #include "itemdescedittab.h" #include "iteminfo.h" #include "itempropertiestab.h" #include "itempropertiesmetadatatab.h" #include "itempropertiescolorstab.h" #include "itempropertiesversionstab.h" #include "itemposition.h" #include "tagscache.h" #ifdef HAVE_MARBLE # include "itempropertiesgpstab.h" # include "gpsiteminfosorter.h" #endif // HAVE_MARBLE namespace Digikam { class Q_DECL_HIDDEN ItemPropertiesSideBarDB::Private { public: explicit Private() : dirtyDesceditTab(false), hasPrevious(false), hasNext(false), hasItemInfoOwnership(false), desceditTab(nullptr) { desceditTab = nullptr; versionsHistoryTab = nullptr; dirtyDesceditTab = false; } bool dirtyDesceditTab; bool hasPrevious; bool hasNext; bool hasItemInfoOwnership; ItemInfoList currentInfos; DImageHistory currentHistory; ItemDescEditTab* desceditTab; ItemPropertiesVersionsTab* versionsHistoryTab; }; ItemPropertiesSideBarDB::ItemPropertiesSideBarDB(QWidget* const parent, SidebarSplitter* const splitter, Qt::Edge side, bool mimimizedDefault) : ItemPropertiesSideBar(parent, splitter, side, mimimizedDefault), d(new Private) { d->desceditTab = new ItemDescEditTab(parent); d->versionsHistoryTab = new ItemPropertiesVersionsTab(parent); appendTab(d->desceditTab, QIcon::fromTheme(QLatin1String("edit-text-frame-update")), i18n("Captions")); appendTab(d->versionsHistoryTab, QIcon::fromTheme(QLatin1String("view-catalog")), i18n("Versions")); // ---------------------------------------------------------- connect(this, SIGNAL(signalChangedTab(QWidget*)), this, SLOT(slotChangedTab(QWidget*))); connect(d->desceditTab, SIGNAL(signalNextItem()), this, SIGNAL(signalNextItem())); connect(d->desceditTab, SIGNAL(signalPrevItem()), this, SIGNAL(signalPrevItem())); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)), this, SLOT(slotImageChangeDatabase(ImageChangeset))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), this, SLOT(slotImageTagChanged(ImageTagChangeset))); connect(ItemAttributesWatch::instance(), SIGNAL(signalFileMetadataChanged(QUrl)), this, SLOT(slotFileMetadataChanged(QUrl))); connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotLoadMetadataFilters())); } ItemPropertiesSideBarDB::~ItemPropertiesSideBarDB() { delete d; } void ItemPropertiesSideBarDB::itemChanged(const ItemInfo& info, const QRect& rect, DImg* const img, const DImageHistory& history) { itemChanged(info.fileUrl(), info, rect, img, history); } void ItemPropertiesSideBarDB::itemChanged(const QUrl& url, const QRect& rect, DImg* const img) { itemChanged(url, ItemInfo(), rect, img, DImageHistory()); } void ItemPropertiesSideBarDB::itemChanged(const QUrl& url, const ItemInfo& info, const QRect& rect, DImg* const img, const DImageHistory& history) { if (!url.isValid()) { return; } m_currentURL = url; ItemInfoList list; if (!info.isNull()) { list << info; } itemChanged(list, rect, img, history); } void ItemPropertiesSideBarDB::itemChanged(const ItemInfoList& infos) { if (infos.isEmpty()) { return; } m_currentURL = infos.first().fileUrl(); itemChanged(infos, QRect(), nullptr, DImageHistory()); } void ItemPropertiesSideBarDB::itemChanged(const ItemInfoList& infos, const QRect& rect, DImg* const img, const DImageHistory& history) { m_currentRect = rect; m_image = img; d->currentHistory = history; d->currentInfos = infos; m_dirtyPropertiesTab = false; m_dirtyMetadataTab = false; m_dirtyColorTab = false; m_dirtyGpsTab = false; m_dirtyHistoryTab = false; d->dirtyDesceditTab = false; // slotChangedTab only handles the active tab. // Any tab that holds information reset above shall be reset here, // unless it is the active tab if (getActiveTab() != d->desceditTab) { d->desceditTab->setItem(); } slotChangedTab( getActiveTab() ); } void ItemPropertiesSideBarDB::slotNoCurrentItem() { ItemPropertiesSideBar::slotNoCurrentItem(); // All tabs that store the ItemInfo list and access it after selection change // must release the image info here. slotChangedTab only handles the active tab! d->desceditTab->setItem(); d->currentInfos.clear(); d->dirtyDesceditTab = false; } void ItemPropertiesSideBarDB::populateTags() { d->desceditTab->populateTags(); } void ItemPropertiesSideBarDB::slotChangedTab(QWidget* tab) { setCursor(Qt::WaitCursor); if (tab == m_propertiesTab && !m_dirtyPropertiesTab) { m_propertiesTab->setCurrentURL(m_currentURL); if (d->currentInfos.isEmpty()) { ItemPropertiesSideBar::setImagePropertiesInformation(m_currentURL); } else { setImagePropertiesInformation(m_currentURL); } m_dirtyPropertiesTab = true; } else if (tab == m_metadataTab && !m_dirtyMetadataTab) { if (d->currentInfos.count() > 1) { // No multiple selection supported. Only if all items belong to // the same group display metadata of main item. ItemInfo mainItem = d->currentInfos.singleGroupMainItem(); if (!mainItem.isNull()) { m_metadataTab->setCurrentURL(mainItem.fileUrl()); } else { m_metadataTab->setCurrentURL(); } } else if (m_image) { DMetadata data(m_image->getMetadata()); m_metadataTab->setCurrentData(data, m_currentURL.fileName()); } else { m_metadataTab->setCurrentURL(m_currentURL); } m_dirtyMetadataTab = true; } else if (tab == m_colorTab && !m_dirtyColorTab) { if (d->currentInfos.count() > 1) { // No multiple selection supported. Only if all items belong to // the same group display metadata of main item. ItemInfo mainItem = d->currentInfos.singleGroupMainItem(); if (!mainItem.isNull()) { m_colorTab->setData(mainItem.fileUrl()); } else { m_colorTab->setData(); } } else { m_colorTab->setData(m_currentURL, m_currentRect, m_image); } m_dirtyColorTab = true; } else if (tab == d->desceditTab && !d->dirtyDesceditTab) { if (d->currentInfos.count() == 0) { // Do nothing here. We cannot get data from database ! d->desceditTab->setItem(); } else if (d->currentInfos.count() == 1) { d->desceditTab->setItem(d->currentInfos.first()); } else { d->desceditTab->setItems(d->currentInfos); } d->dirtyDesceditTab = true; } #ifdef HAVE_MARBLE else if (tab == m_gpsTab && !m_dirtyGpsTab) { if (d->currentInfos.count() == 0) { m_gpsTab->setCurrentURL(m_currentURL); } else { GPSItemInfo::List list; for (ItemInfoList::const_iterator it = d->currentInfos.constBegin(); it != d->currentInfos.constEnd(); ++it) { GPSItemInfo info; if (GPSItemInfofromItemInfo(*it, &info)) { list << info; } } if (list.isEmpty()) { m_gpsTab->setCurrentURL(); } else { m_gpsTab->setGPSInfoList(list); } } m_dirtyGpsTab = true; } #endif // HAVE_MARBLE else if (tab == d->versionsHistoryTab && !m_dirtyHistoryTab) { //TODO: Make a database-less parent class with only the filters tab if (d->currentInfos.count() == 0 || d->currentInfos.count() > 1) { // FIXME: Any sensible multi-selection functionality? Must scale for large n! d->versionsHistoryTab->clear(); } else { d->versionsHistoryTab->setItem(d->currentInfos.first(), d->currentHistory); } m_dirtyHistoryTab = true; } #ifdef HAVE_MARBLE m_gpsTab->setActive(tab == m_gpsTab); #endif // HAVE_MARBLE unsetCursor(); } void ItemPropertiesSideBarDB::slotFileMetadataChanged(const QUrl& url) { if (url == m_currentURL) { // trigger an update m_dirtyMetadataTab = false; if (getActiveTab() == m_metadataTab) { // update now - reuse code form slotChangedTab slotChangedTab( getActiveTab() ); } } } void ItemPropertiesSideBarDB::slotImageChangeDatabase(const ImageChangeset& changeset) { if (!d->currentInfos.isEmpty()) { QWidget* const tab = getActiveTab(); if (!tab) { return; } if (tab == m_propertiesTab #ifdef HAVE_MARBLE || tab == m_gpsTab #endif // HAVE_MARBLE ) { ItemInfo& info = d->currentInfos.first(); if (changeset.ids().contains(info.id())) { // trigger an update, if changes touch the tab's information DatabaseFields::Set set = changeset.changes(); if ((set & DatabaseFields::ImagesAll) || (set & DatabaseFields::ItemInformationAll) || (set & DatabaseFields::ImageMetadataAll) || (set & DatabaseFields::VideoMetadataAll) || (set & DatabaseFields::ItemCommentsAll)) { m_dirtyPropertiesTab = false; } else if (set & DatabaseFields::ItemPositionsAll) { m_dirtyGpsTab = false; } if (tab == m_propertiesTab #ifdef HAVE_MARBLE || tab == m_gpsTab #endif // HAVE_MARBLE ) { // update now - reuse code form slotChangedTab slotChangedTab(tab); } } } } } void ItemPropertiesSideBarDB::slotImageTagChanged(const ImageTagChangeset& changeset) { if (!d->currentInfos.isEmpty()) { QWidget* const tab = getActiveTab(); if (!tab) { return; } if (tab == m_propertiesTab) { ItemInfo& info = d->currentInfos.first(); if (changeset.ids().contains(info.id())) { m_dirtyPropertiesTab = false; slotChangedTab(tab); } } } } void ItemPropertiesSideBarDB::slotAssignRating(int rating) { d->desceditTab->assignRating(rating); } void ItemPropertiesSideBarDB::slotAssignRatingNoStar() { d->desceditTab->assignRating(0); } void ItemPropertiesSideBarDB::slotAssignRatingOneStar() { d->desceditTab->assignRating(1); } void ItemPropertiesSideBarDB::slotAssignRatingTwoStar() { d->desceditTab->assignRating(2); } void ItemPropertiesSideBarDB::slotAssignRatingThreeStar() { d->desceditTab->assignRating(3); } void ItemPropertiesSideBarDB::slotAssignRatingFourStar() { d->desceditTab->assignRating(4); } void ItemPropertiesSideBarDB::slotAssignRatingFiveStar() { d->desceditTab->assignRating(5); } void ItemPropertiesSideBarDB::refreshTagsView() { // TODO update, do we still need this method? //d->desceditTab->refreshTagsView(); } void ItemPropertiesSideBarDB::setImagePropertiesInformation(const QUrl& url) { - foreach(const ItemInfo& info, d->currentInfos) + foreach (const ItemInfo& info, d->currentInfos) { if (info.fileUrl() == url) { QString str; QString unavailable(i18n("unavailable")); QFileInfo fileInfo(url.toLocalFile()); // -- File system information ----------------------------------------- ImageCommonContainer commonInfo = info.imageCommonContainer(); ImageMetadataContainer photoInfo = info.imageMetadataContainer(); VideoMetadataContainer videoInfo = info.videoMetadataContainer(); str = QLocale().toString(commonInfo.fileModificationDate, QLocale::ShortFormat); m_propertiesTab->setFileModifiedDate(str); str = QString::fromUtf8("%1 (%2)").arg(ItemPropertiesTab::humanReadableBytesCount(fileInfo.size())) .arg(QLocale().toString(commonInfo.fileSize)); m_propertiesTab->setFileSize(str); // These infos are not stored in DB m_propertiesTab->setFileOwner(QString::fromUtf8("%1 - %2").arg(fileInfo.owner()).arg(fileInfo.group())); m_propertiesTab->setFilePermissions(ItemPropertiesTab::permissionsString(fileInfo)); // -- Image Properties -------------------------------------------------- if (commonInfo.width == 0 || commonInfo.height == 0) { str = i18n("Unknown"); } else { QString mpixels; mpixels.setNum(commonInfo.width * commonInfo.height / 1000000.0, 'f', 2); str = i18nc("width x height (megapixels Mpx)", "%1x%2 (%3Mpx)", commonInfo.width, commonInfo.height, mpixels); } m_propertiesTab->setItemDimensions(str); if (commonInfo.width == 0 || commonInfo.height == 0) { str = i18n("Unknown"); } else { m_propertiesTab->aspectRatioToString(commonInfo.width, commonInfo.height, str); } m_propertiesTab->setImageRatio(str); m_propertiesTab->setImageMime(commonInfo.format); m_propertiesTab->setImageBitDepth(i18n("%1 bpp", commonInfo.colorDepth)); m_propertiesTab->setHasSidecar(DMetadata::hasSidecar(url.toLocalFile()) ? i18n("Yes") : i18n("No")); m_propertiesTab->setImageColorMode(commonInfo.colorModel.isEmpty() ? unavailable : commonInfo.colorModel); // -- Photograph information ------------------------------------------ m_propertiesTab->setPhotoInfoDisable(photoInfo.allFieldsNull); ItemPropertiesTab::shortenedMakeInfo(photoInfo.make); ItemPropertiesTab::shortenedModelInfo(photoInfo.model); m_propertiesTab->setPhotoMake(photoInfo.make.isEmpty() ? unavailable : photoInfo.make); m_propertiesTab->setPhotoModel(photoInfo.model.isEmpty() ? unavailable : photoInfo.model); if (commonInfo.creationDate.isValid()) { str = QLocale().toString(commonInfo.creationDate, QLocale::ShortFormat); m_propertiesTab->setPhotoDateTime(str); } else { m_propertiesTab->setPhotoDateTime(unavailable); } m_propertiesTab->setPhotoLens(photoInfo.lens.isEmpty() ? unavailable : photoInfo.lens); m_propertiesTab->setPhotoAperture(photoInfo.aperture.isEmpty() ? unavailable : photoInfo.aperture); if (photoInfo.focalLength35.isEmpty()) { m_propertiesTab->setPhotoFocalLength(photoInfo.focalLength.isEmpty() ? unavailable : photoInfo.focalLength); } else { str = i18n("%1 (%2)", photoInfo.focalLength, photoInfo.focalLength35); m_propertiesTab->setPhotoFocalLength(str); } m_propertiesTab->setPhotoExposureTime(photoInfo.exposureTime.isEmpty() ? unavailable : photoInfo.exposureTime); m_propertiesTab->setPhotoSensitivity(photoInfo.sensitivity.isEmpty() ? unavailable : i18n("%1 ISO", photoInfo.sensitivity)); if (photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) { m_propertiesTab->setPhotoExposureMode(unavailable); } else if (!photoInfo.exposureMode.isEmpty() && photoInfo.exposureProgram.isEmpty()) { m_propertiesTab->setPhotoExposureMode(photoInfo.exposureMode); } else if (photoInfo.exposureMode.isEmpty() && !photoInfo.exposureProgram.isEmpty()) { m_propertiesTab->setPhotoExposureMode(photoInfo.exposureProgram); } else { str = QString::fromUtf8("%1 / %2").arg(photoInfo.exposureMode).arg(photoInfo.exposureProgram); m_propertiesTab->setPhotoExposureMode(str); } m_propertiesTab->setPhotoFlash(photoInfo.flashMode.isEmpty() ? unavailable : photoInfo.flashMode); m_propertiesTab->setPhotoWhiteBalance(photoInfo.whiteBalance.isEmpty() ? unavailable : photoInfo.whiteBalance); // -- Audio/Video Metadata information ------------------------ m_propertiesTab->setVideoInfoDisable(videoInfo.allFieldsNull); m_propertiesTab->setVideoAspectRatio(videoInfo.aspectRatio.isEmpty() ? unavailable : videoInfo.aspectRatio); m_propertiesTab->setVideoDuration(videoInfo.duration.isEmpty() ? unavailable : videoInfo.duration); m_propertiesTab->setVideoFrameRate(videoInfo.frameRate.isEmpty() ? unavailable : videoInfo.frameRate); m_propertiesTab->setVideoVideoCodec(videoInfo.videoCodec.isEmpty() ? unavailable : videoInfo.videoCodec); m_propertiesTab->setVideoAudioBitRate(videoInfo.audioBitRate.isEmpty() ? unavailable : videoInfo.audioBitRate); m_propertiesTab->setVideoAudioChannelType(videoInfo.audioChannelType.isEmpty() ? unavailable : videoInfo.audioChannelType); m_propertiesTab->setVideoAudioCodec(videoInfo.audioCodec.isEmpty() ? unavailable : videoInfo.audioCodec); // -- Caption / Tags ------------------------------------------ m_propertiesTab->setCaption(info.comment()); m_propertiesTab->setPickLabel(info.pickLabel()); m_propertiesTab->setColorLabel(info.colorLabel()); m_propertiesTab->setRating(info.rating()); QList tagIds = info.tagIds(); m_propertiesTab->setTags(TagsCache::instance()->tagPaths(tagIds, TagsCache::NoLeadingSlash, TagsCache::NoHiddenTags), TagsCache::instance()->tagNames(tagIds, TagsCache::NoHiddenTags)); m_propertiesTab->showOrHideCaptionAndTags(); return; } } } ItemPropertiesVersionsTab* ItemPropertiesSideBarDB::getFiltersHistoryTab() const { return d->versionsHistoryTab; } ItemDescEditTab* ItemPropertiesSideBarDB::imageDescEditTab() const { return d->desceditTab; } void ItemPropertiesSideBarDB::doLoadState() { ItemPropertiesSideBar::doLoadState(); KConfigGroup group = getConfigGroup(); KConfigGroup groupVersionTab = KConfigGroup(&group, entryName(QLatin1String("Version Properties Tab"))); d->versionsHistoryTab->readSettings(groupVersionTab); KConfigGroup groupCaptionsTagsTab = KConfigGroup(&group, entryName(QLatin1String("Captions Tags Properties Tab"))); d->desceditTab->readSettings(groupCaptionsTagsTab); } void ItemPropertiesSideBarDB::doSaveState() { ItemPropertiesSideBar::doSaveState(); KConfigGroup group = getConfigGroup(); KConfigGroup groupVersionTab = KConfigGroup(&group, entryName(QLatin1String("Version Properties Tab"))); d->versionsHistoryTab->writeSettings(groupVersionTab); KConfigGroup groupCaptionsTagsTab = KConfigGroup(&group, entryName(QLatin1String("Captions Tags Properties Tab"))); d->desceditTab->writeSettings(groupCaptionsTagsTab); } void ItemPropertiesSideBarDB::slotPopupTagsView() { setActiveTab(d->desceditTab); d->desceditTab->setFocusToTagsView(); } #ifdef HAVE_MARBLE bool ItemPropertiesSideBarDB::GPSItemInfofromItemInfo(const ItemInfo& imageInfo, GPSItemInfo* const gpsItemInfo) { const ItemPosition pos = imageInfo.imagePosition(); if (pos.isEmpty() || !pos.hasCoordinates()) { return false; } gpsItemInfo->coordinates.setLatLon(pos.latitudeNumber(), pos.longitudeNumber()); if (pos.hasAltitude()) { gpsItemInfo->coordinates.setAlt(pos.altitude()); } gpsItemInfo->dateTime = imageInfo.dateTime(); gpsItemInfo->rating = imageInfo.rating(); gpsItemInfo->url = imageInfo.fileUrl(); gpsItemInfo->id = imageInfo.id(); return true; } #endif // HAVE_MARBLE } // namespace Digikam diff --git a/core/libs/properties/itempropertiestab.cpp b/core/libs/properties/itempropertiestab.cpp index c8b926e316..48beef60cb 100644 --- a/core/libs/properties/itempropertiestab.cpp +++ b/core/libs/properties/itempropertiestab.cpp @@ -1,964 +1,964 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-04-19 * Description : A tab to display general item information * * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2013 by Michael G. Hansen * * 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, 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. * * ============================================================ */ #include "itempropertiestab.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "itempropertiestxtlabel.h" #include "picklabelwidget.h" #include "colorlabelwidget.h" #include "tagscache.h" namespace Digikam { class Q_DECL_HIDDEN ItemPropertiesTab::Private { public: enum Section { FileProperties = 0, ImageProperties, PhotoProperties, VideoProperties, digiKamProperties }; public: explicit Private() : caption(nullptr), pickLabel(nullptr), colorLabel(nullptr), rating(nullptr), tags(nullptr), labelFile(nullptr), labelFolder(nullptr), labelFileModifiedDate(nullptr), labelFileSize(nullptr), labelFileOwner(nullptr), labelFilePermissions(nullptr), labelImageMime(nullptr), labelImageDimensions(nullptr), labelImageRatio(nullptr), labelImageBitDepth(nullptr), labelImageColorMode(nullptr), labelHasSidecar(nullptr), labelPhotoMake(nullptr), labelPhotoModel(nullptr), labelPhotoDateTime(nullptr), labelPhotoLens(nullptr), labelPhotoAperture(nullptr), labelPhotoFocalLength(nullptr), labelPhotoExposureTime(nullptr), labelPhotoSensitivity(nullptr), labelPhotoExposureMode(nullptr), labelPhotoFlash(nullptr), labelPhotoWhiteBalance(nullptr), labelCaption(nullptr), labelTags(nullptr), labelPickLabel(nullptr), labelColorLabel(nullptr), labelRating(nullptr), labelVideoAspectRatio(nullptr), labelVideoDuration(nullptr), labelVideoFrameRate(nullptr), labelVideoVideoCodec(nullptr), labelVideoAudioBitRate(nullptr), labelVideoAudioChannelType(nullptr), labelVideoAudioCodec(nullptr) { } DTextLabelName* caption; DTextLabelName* pickLabel; DTextLabelName* colorLabel; DTextLabelName* rating; DTextLabelName* tags; DTextLabelValue* labelFile; DTextLabelValue* labelFolder; DTextLabelValue* labelFileModifiedDate; DTextLabelValue* labelFileSize; DTextLabelValue* labelFileOwner; DTextLabelValue* labelFilePermissions; DTextLabelValue* labelImageMime; DTextLabelValue* labelImageDimensions; DTextLabelValue* labelImageRatio; DTextLabelValue* labelImageBitDepth; DTextLabelValue* labelImageColorMode; DTextLabelValue* labelHasSidecar; DTextLabelValue* labelPhotoMake; DTextLabelValue* labelPhotoModel; DTextLabelValue* labelPhotoDateTime; DTextLabelValue* labelPhotoLens; DTextLabelValue* labelPhotoAperture; DTextLabelValue* labelPhotoFocalLength; DTextLabelValue* labelPhotoExposureTime; DTextLabelValue* labelPhotoSensitivity; DTextLabelValue* labelPhotoExposureMode; DTextLabelValue* labelPhotoFlash; DTextLabelValue* labelPhotoWhiteBalance; DTextLabelValue* labelCaption; DTextLabelValue* labelTags; DTextLabelValue* labelPickLabel; DTextLabelValue* labelColorLabel; DTextLabelValue* labelRating; DTextLabelValue* labelVideoAspectRatio; DTextLabelValue* labelVideoDuration; DTextLabelValue* labelVideoFrameRate; DTextLabelValue* labelVideoVideoCodec; DTextLabelValue* labelVideoAudioBitRate; DTextLabelValue* labelVideoAudioChannelType; DTextLabelValue* labelVideoAudioCodec; }; ItemPropertiesTab::ItemPropertiesTab(QWidget* const parent) : DExpanderBox(parent), d(new Private) { setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); setLineWidth(style()->pixelMetric(QStyle::PM_DefaultFrameWidth)); // -------------------------------------------------- const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); QWidget* const w1 = new QWidget(this); QGridLayout* const glay1 = new QGridLayout(w1); DTextLabelName* const file = new DTextLabelName(i18n("File: "), w1); DTextLabelName* const folder = new DTextLabelName(i18n("Folder: "), w1); DTextLabelName* const modifiedDate = new DTextLabelName(i18n("Date: "), w1); DTextLabelName* const size = new DTextLabelName(i18n("Size: "), w1); DTextLabelName* const owner = new DTextLabelName(i18n("Owner: "), w1); DTextLabelName* const permissions = new DTextLabelName(i18n("Permissions: "), w1); d->labelFile = new DTextLabelValue(QString(), w1); d->labelFolder = new DTextLabelValue(QString(), w1); d->labelFileModifiedDate = new DTextLabelValue(QString(), w1); d->labelFileSize = new DTextLabelValue(QString(), w1); d->labelFileOwner = new DTextLabelValue(QString(), w1); d->labelFilePermissions = new DTextLabelValue(QString(), w1); glay1->addWidget(file, 0, 0, 1, 1); glay1->addWidget(d->labelFile, 0, 1, 1, 1); glay1->addWidget(folder, 1, 0, 1, 1); glay1->addWidget(d->labelFolder, 1, 1, 1, 1); glay1->addWidget(modifiedDate, 2, 0, 1, 1); glay1->addWidget(d->labelFileModifiedDate, 2, 1, 1, 1); glay1->addWidget(size, 3, 0, 1, 1); glay1->addWidget(d->labelFileSize, 3, 1, 1, 1); glay1->addWidget(owner, 4, 0, 1, 1); glay1->addWidget(d->labelFileOwner, 4, 1, 1, 1); glay1->addWidget(permissions, 5, 0, 1, 1); glay1->addWidget(d->labelFilePermissions, 5, 1, 1, 1); glay1->setContentsMargins(spacing, spacing, spacing, spacing); glay1->setColumnStretch(0, 10); glay1->setColumnStretch(1, 10); glay1->setSpacing(0); insertItem(ItemPropertiesTab::Private::FileProperties, w1, QIcon::fromTheme(QLatin1String("dialog-information")), i18n("File Properties"), QLatin1String("FileProperties"), true); // -------------------------------------------------- QWidget* const w2 = new QWidget(this); QGridLayout* const glay2 = new QGridLayout(w2); DTextLabelName* const mime = new DTextLabelName(i18n("Type: "), w2); DTextLabelName* const dimensions = new DTextLabelName(i18n("Dimensions: "), w2); DTextLabelName* const ratio = new DTextLabelName(i18n("Aspect Ratio: "), w2); DTextLabelName* const bitDepth = new DTextLabelName(i18n("Bit depth: "), w2); DTextLabelName* const colorMode = new DTextLabelName(i18n("Color mode: "), w2); DTextLabelName* const hasSidecar = new DTextLabelName(i18n("Sidecar: "), w2); d->labelImageMime = new DTextLabelValue(QString(), w2); d->labelImageDimensions = new DTextLabelValue(QString(), w2); d->labelImageRatio = new DTextLabelValue(QString(), w2); d->labelImageBitDepth = new DTextLabelValue(QString(), w2); d->labelImageColorMode = new DTextLabelValue(QString(), w2); d->labelHasSidecar = new DTextLabelValue(QString(), w2); glay2->addWidget(mime, 0, 0, 1, 1); glay2->addWidget(d->labelImageMime, 0, 1, 1, 1); glay2->addWidget(dimensions, 1, 0, 1, 1); glay2->addWidget(d->labelImageDimensions, 1, 1, 1, 1); glay2->addWidget(ratio, 2, 0, 1, 1); glay2->addWidget(d->labelImageRatio, 2, 1, 1, 1); glay2->addWidget(bitDepth, 3, 0, 1, 1); glay2->addWidget(d->labelImageBitDepth, 3, 1, 1, 1); glay2->addWidget(colorMode, 4, 0, 1, 1); glay2->addWidget(d->labelImageColorMode, 4, 1, 1, 1); glay2->addWidget(hasSidecar, 5, 0, 1, 1); glay2->addWidget(d->labelHasSidecar, 5, 1, 1, 1); glay2->setContentsMargins(spacing, spacing, spacing, spacing); glay2->setColumnStretch(0, 10); glay2->setColumnStretch(1, 10); glay2->setSpacing(0); insertItem(ItemPropertiesTab::Private::ImageProperties, w2, QIcon::fromTheme(QLatin1String("view-preview")), i18n("Item Properties"), QLatin1String("ItemProperties"), true); // -------------------------------------------------- QWidget* const w3 = new QWidget(this); QGridLayout* const glay3 = new QGridLayout(w3); DTextLabelName* const make = new DTextLabelName(i18n("Make: "), w3); DTextLabelName* const model = new DTextLabelName(i18n("Model: "), w3); DTextLabelName* const photoDate = new DTextLabelName(i18n("Created: "), w3); DTextLabelName* const lens = new DTextLabelName(i18n("Lens: "), w3); DTextLabelName* const aperture = new DTextLabelName(i18n("Aperture: "), w3); DTextLabelName* const focalLength = new DTextLabelName(i18n("Focal: "), w3); DTextLabelName* const exposureTime = new DTextLabelName(i18n("Exposure: "), w3); DTextLabelName* const sensitivity = new DTextLabelName(i18n("Sensitivity: "), w3); DTextLabelName* const exposureMode = new DTextLabelName(i18n("Mode/Program: "), w3); DTextLabelName* const flash = new DTextLabelName(i18n("Flash: "), w3); DTextLabelName* const whiteBalance = new DTextLabelName(i18n("White balance: "), w3); d->labelPhotoMake = new DTextLabelValue(QString(), w3); d->labelPhotoModel = new DTextLabelValue(QString(), w3); d->labelPhotoDateTime = new DTextLabelValue(QString(), w3); d->labelPhotoLens = new DTextLabelValue(QString(), w3); d->labelPhotoAperture = new DTextLabelValue(QString(), w3); d->labelPhotoFocalLength = new DTextLabelValue(QString(), w3); d->labelPhotoExposureTime = new DTextLabelValue(QString(), w3); d->labelPhotoSensitivity = new DTextLabelValue(QString(), w3); d->labelPhotoExposureMode = new DTextLabelValue(QString(), w3); d->labelPhotoFlash = new DTextLabelValue(QString(), w3); d->labelPhotoWhiteBalance = new DTextLabelValue(QString(), w3); glay3->addWidget(make, 0, 0, 1, 1); glay3->addWidget(d->labelPhotoMake, 0, 1, 1, 1); glay3->addWidget(model, 1, 0, 1, 1); glay3->addWidget(d->labelPhotoModel, 1, 1, 1, 1); glay3->addWidget(photoDate, 2, 0, 1, 1); glay3->addWidget(d->labelPhotoDateTime, 2, 1, 1, 1); glay3->addWidget(lens, 3, 0, 1, 1); glay3->addWidget(d->labelPhotoLens, 3, 1, 1, 1); glay3->addWidget(aperture, 4, 0, 1, 1); glay3->addWidget(d->labelPhotoAperture, 4, 1, 1, 1); glay3->addWidget(focalLength, 5, 0, 1, 1); glay3->addWidget(d->labelPhotoFocalLength, 5, 1, 1, 1); glay3->addWidget(exposureTime, 6, 0, 1, 1); glay3->addWidget(d->labelPhotoExposureTime, 6, 1, 1, 1); glay3->addWidget(sensitivity, 7, 0, 1, 1); glay3->addWidget(d->labelPhotoSensitivity, 7, 1, 1, 1); glay3->addWidget(exposureMode, 8, 0, 1, 1); glay3->addWidget(d->labelPhotoExposureMode, 8, 1, 1, 1); glay3->addWidget(flash, 9, 0, 1, 1); glay3->addWidget(d->labelPhotoFlash, 9, 1, 1, 1); glay3->addWidget(whiteBalance, 10, 0, 1, 1); glay3->addWidget(d->labelPhotoWhiteBalance, 10, 1, 1, 1); glay3->setContentsMargins(spacing, spacing, spacing, spacing); glay3->setColumnStretch(0, 10); glay3->setColumnStretch(1, 10); glay3->setSpacing(0); insertItem(ItemPropertiesTab::Private::PhotoProperties, w3, QIcon::fromTheme(QLatin1String("camera-photo")), i18n("Photograph Properties"), QLatin1String("PhotographProperties"), true); // -------------------------------------------------- QWidget* const w4 = new QWidget(this); QGridLayout* const glay4 = new QGridLayout(w4); DTextLabelName* const aspectRatio = new DTextLabelName(i18n("Aspect Ratio: "), w4); DTextLabelName* const duration = new DTextLabelName(i18n("Duration: "), w4); DTextLabelName* const frameRate = new DTextLabelName(i18n("Frame Rate: "), w4); DTextLabelName* const videoCodec = new DTextLabelName(i18n("Video Codec: "), w4); DTextLabelName* const audioBitRate = new DTextLabelName(i18n("Audio Bit Rate: "), w4); DTextLabelName* const audioChannelType = new DTextLabelName(i18n("Audio Channel Type: "), w4); DTextLabelName* const audioCodec = new DTextLabelName(i18n("Audio Codec: "), w4); d->labelVideoAspectRatio = new DTextLabelValue(QString(), w4); d->labelVideoDuration = new DTextLabelValue(QString(), w4); d->labelVideoFrameRate = new DTextLabelValue(QString(), w4); d->labelVideoVideoCodec = new DTextLabelValue(QString(), w4); d->labelVideoAudioBitRate = new DTextLabelValue(QString(), w4); d->labelVideoAudioChannelType = new DTextLabelValue(QString(), w4); d->labelVideoAudioCodec = new DTextLabelValue(QString(), w4); glay4->addWidget(aspectRatio, 0, 0, 1, 1); glay4->addWidget(d->labelVideoAspectRatio, 0, 1, 1, 1); glay4->addWidget(duration, 1, 0, 1, 1); glay4->addWidget(d->labelVideoDuration, 1, 1, 1, 1); glay4->addWidget(frameRate, 2, 0, 1, 1); glay4->addWidget(d->labelVideoFrameRate, 2, 1, 1, 1); glay4->addWidget(videoCodec, 3, 0, 1, 1); glay4->addWidget(d->labelVideoVideoCodec, 3, 1, 1, 1); glay4->addWidget(audioBitRate, 4, 0, 1, 1); glay4->addWidget(d->labelVideoAudioBitRate, 4, 1, 1, 1); glay4->addWidget(audioChannelType, 5, 0, 1, 1); glay4->addWidget(d->labelVideoAudioChannelType, 5, 1, 1, 1); glay4->addWidget(audioCodec, 6, 0, 1, 1); glay4->addWidget(d->labelVideoAudioCodec, 6, 1, 1, 1); glay4->setContentsMargins(spacing, spacing, spacing, spacing); glay4->setColumnStretch(0, 10); glay4->setColumnStretch(1, 10); glay4->setSpacing(0); insertItem(ItemPropertiesTab::Private::VideoProperties, w4, QIcon::fromTheme(QLatin1String("video-x-generic")), i18n("Audio/Video Properties"), QLatin1String("VideoProperties"), true); // -------------------------------------------------- QWidget* const w5 = new QWidget(this); QGridLayout* const glay5 = new QGridLayout(w5); d->caption = new DTextLabelName(i18n("Caption: "), w5); d->pickLabel = new DTextLabelName(i18n("Pick label: "), w5); d->colorLabel = new DTextLabelName(i18n("Color label: "), w5); d->rating = new DTextLabelName(i18n("Rating: "), w5); d->tags = new DTextLabelName(i18n("Tags: "), w5); d->labelCaption = new DTextLabelValue(QString(), w5); d->labelPickLabel = new DTextLabelValue(QString(), w5); d->labelColorLabel = new DTextLabelValue(QString(), w5); d->labelRating = new DTextLabelValue(QString(), w5); d->labelTags = new DTextLabelValue(QString(), w5); d->labelTags->setElideMode(Qt::ElideLeft); glay5->addWidget(d->caption, 0, 0, 1, 1); glay5->addWidget(d->labelCaption, 0, 1, 1, 1); glay5->addWidget(d->tags, 1, 0, 1, 1); glay5->addWidget(d->labelTags, 1, 1, 1, 1); glay5->addWidget(d->pickLabel, 2, 0, 1, 1); glay5->addWidget(d->labelPickLabel, 2, 1, 1, 1); glay5->addWidget(d->colorLabel, 3, 0, 1, 1); glay5->addWidget(d->labelColorLabel, 3, 1, 1, 1); glay5->addWidget(d->rating, 4, 0, 1, 1); glay5->addWidget(d->labelRating, 4, 1, 1, 1); glay5->setContentsMargins(spacing, spacing, spacing, spacing); glay5->setColumnStretch(0, 10); glay5->setColumnStretch(1, 10); glay5->setSpacing(0); insertItem(ItemPropertiesTab::Private::digiKamProperties, w5, QIcon::fromTheme(QLatin1String("edit-text-frame-update")), i18n("digiKam Properties"), QLatin1String("DigikamProperties"), true); // -------------------------------------------------- addStretch(); } ItemPropertiesTab::~ItemPropertiesTab() { delete d; } void ItemPropertiesTab::setCurrentURL(const QUrl& url) { if (url.isEmpty()) { d->labelFile->setAdjustedText(); d->labelFolder->setAdjustedText(); d->labelFileModifiedDate->setAdjustedText(); d->labelFileSize->setAdjustedText(); d->labelFileOwner->setAdjustedText(); d->labelFilePermissions->setAdjustedText(); d->labelImageMime->setAdjustedText(); d->labelImageDimensions->setAdjustedText(); d->labelImageRatio->setAdjustedText(); d->labelImageBitDepth->setAdjustedText(); d->labelImageColorMode->setAdjustedText(); d->labelHasSidecar->setAdjustedText(); d->labelPhotoMake->setAdjustedText(); d->labelPhotoModel->setAdjustedText(); d->labelPhotoDateTime->setAdjustedText(); d->labelPhotoLens->setAdjustedText(); d->labelPhotoAperture->setAdjustedText(); d->labelPhotoFocalLength->setAdjustedText(); d->labelPhotoExposureTime->setAdjustedText(); d->labelPhotoSensitivity->setAdjustedText(); d->labelPhotoExposureMode->setAdjustedText(); d->labelPhotoFlash->setAdjustedText(); d->labelPhotoWhiteBalance->setAdjustedText(); d->labelCaption->setAdjustedText(); d->labelPickLabel->setAdjustedText(); d->labelColorLabel->setAdjustedText(); d->labelRating->setAdjustedText(); d->labelTags->setAdjustedText(); d->labelVideoAspectRatio->setAdjustedText(); d->labelVideoDuration->setAdjustedText(); d->labelVideoFrameRate->setAdjustedText(); d->labelVideoVideoCodec->setAdjustedText(); d->labelVideoAudioBitRate->setAdjustedText(); d->labelVideoAudioChannelType->setAdjustedText(); d->labelVideoAudioCodec->setAdjustedText(); setEnabled(false); return; } setEnabled(true); d->labelFile->setAdjustedText(url.fileName()); d->labelFolder->setAdjustedText(QDir::toNativeSeparators(url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).toLocalFile())); } void ItemPropertiesTab::setPhotoInfoDisable(const bool b) { if (b) { widget(ItemPropertiesTab::Private::PhotoProperties)->hide(); } else { widget(ItemPropertiesTab::Private::PhotoProperties)->show(); } } void ItemPropertiesTab::setVideoInfoDisable(const bool b) { if (b) { widget(ItemPropertiesTab::Private::VideoProperties)->hide(); } else { widget(ItemPropertiesTab::Private::VideoProperties)->show(); } } void ItemPropertiesTab::setFileModifiedDate(const QString& str) { d->labelFileModifiedDate->setAdjustedText(str); } void ItemPropertiesTab::setFileSize(const QString& str) { d->labelFileSize->setAdjustedText(str); } void ItemPropertiesTab::setFileOwner(const QString& str) { d->labelFileOwner->setAdjustedText(str); } void ItemPropertiesTab::setFilePermissions(const QString& str) { d->labelFilePermissions->setAdjustedText(str); } void ItemPropertiesTab::setImageMime(const QString& str) { d->labelImageMime->setAdjustedText(str); } void ItemPropertiesTab::setItemDimensions(const QString& str) { d->labelImageDimensions->setAdjustedText(str); } void ItemPropertiesTab::setImageRatio(const QString& str) { d->labelImageRatio->setAdjustedText(str); } void ItemPropertiesTab::setImageBitDepth(const QString& str) { d->labelImageBitDepth->setAdjustedText(str); } void ItemPropertiesTab::setImageColorMode(const QString& str) { d->labelImageColorMode->setAdjustedText(str); } void ItemPropertiesTab::setHasSidecar(const QString& str) { d->labelHasSidecar->setAdjustedText(str); } void ItemPropertiesTab::setPhotoMake(const QString& str) { d->labelPhotoMake->setAdjustedText(str); } void ItemPropertiesTab::setPhotoModel(const QString& str) { d->labelPhotoModel->setAdjustedText(str); } void ItemPropertiesTab::setPhotoDateTime(const QString& str) { d->labelPhotoDateTime->setAdjustedText(str); } void ItemPropertiesTab::setPhotoLens(const QString& str) { d->labelPhotoLens->setAdjustedText(str); } void ItemPropertiesTab::setPhotoAperture(const QString& str) { d->labelPhotoAperture->setAdjustedText(str); } void ItemPropertiesTab::setPhotoFocalLength(const QString& str) { d->labelPhotoFocalLength->setAdjustedText(str); } void ItemPropertiesTab::setPhotoExposureTime(const QString& str) { d->labelPhotoExposureTime->setAdjustedText(str); } void ItemPropertiesTab::setPhotoSensitivity(const QString& str) { d->labelPhotoSensitivity->setAdjustedText(str); } void ItemPropertiesTab::setPhotoExposureMode(const QString& str) { d->labelPhotoExposureMode->setAdjustedText(str); } void ItemPropertiesTab::setPhotoFlash(const QString& str) { d->labelPhotoFlash->setAdjustedText(str); } void ItemPropertiesTab::setPhotoWhiteBalance(const QString& str) { d->labelPhotoWhiteBalance->setAdjustedText(str); } void ItemPropertiesTab::showOrHideCaptionAndTags() { bool hasCaption = !d->labelCaption->adjustedText().isEmpty(); bool hasPickLabel = !d->labelPickLabel->adjustedText().isEmpty(); bool hasColorLabel = !d->labelColorLabel->adjustedText().isEmpty(); bool hasRating = !d->labelRating->adjustedText().isEmpty(); bool hasTags = !d->labelTags->adjustedText().isEmpty(); d->caption->setVisible(hasCaption); d->labelCaption->setVisible(hasCaption); d->pickLabel->setVisible(hasPickLabel); d->labelPickLabel->setVisible(hasPickLabel); d->colorLabel->setVisible(hasColorLabel); d->labelColorLabel->setVisible(hasColorLabel); d->rating->setVisible(hasRating); d->labelRating->setVisible(hasRating); d->tags->setVisible(hasTags); d->labelTags->setVisible(hasTags); widget(ItemPropertiesTab::Private::digiKamProperties)->setVisible(hasCaption || hasRating || hasTags || hasPickLabel || hasColorLabel); } void ItemPropertiesTab::setCaption(const QString& str) { d->labelCaption->setAdjustedText(str); } void ItemPropertiesTab::setColorLabel(int colorId) { if (colorId == NoColorLabel) { d->labelColorLabel->setAdjustedText(QString()); } else { d->labelColorLabel->setAdjustedText(ColorLabelWidget::labelColorName((ColorLabel)colorId)); } } void ItemPropertiesTab::setPickLabel(int pickId) { if (pickId == NoPickLabel) { d->labelPickLabel->setAdjustedText(QString()); } else { d->labelPickLabel->setAdjustedText(PickLabelWidget::labelPickName((PickLabel)pickId)); } } void ItemPropertiesTab::setRating(int rating) { QString str; if ((rating > RatingMin) && (rating <= RatingMax)) { str = QLatin1Char(' '); for (int i = 0 ; i < rating ; ++i) { str += QChar(0x2730); str += QLatin1Char(' '); } } d->labelRating->setAdjustedText(str); } void ItemPropertiesTab::setVideoAspectRatio(const QString& str) { d->labelVideoAspectRatio->setAdjustedText(str); } void ItemPropertiesTab::setVideoAudioBitRate(const QString& str) { // use string given as parameter by default because it contains the value for "unavailable" if needed QString audioBitRateString = str; bool ok = false; const int audioBitRateInt = str.toInt(&ok); if (ok) { audioBitRateString = QLocale().toString(audioBitRateInt); } d->labelVideoAudioBitRate->setAdjustedText(audioBitRateString); } void ItemPropertiesTab::setVideoAudioChannelType(const QString& str) { d->labelVideoAudioChannelType->setAdjustedText(str); } void ItemPropertiesTab::setVideoAudioCodec(const QString& str) { d->labelVideoAudioCodec->setAdjustedText(str); } void ItemPropertiesTab::setVideoDuration(const QString& str) { // duration is given as a string in milliseconds // use string given as parameter by default because it contains the value for "unavailable" if needed QString durationString = str; bool ok = false; const int durationVal = str.toInt(&ok); if (ok) { unsigned int r, d, h, m, s, f; r = qAbs(durationVal); d = r / 86400000; r = r % 86400000; h = r / 3600000; r = r % 3600000; m = r / 60000; r = r % 60000; s = r / 1000; f = r % 1000; durationString = QString().sprintf("%d.%02d:%02d:%02d.%03d", d, h, m, s, f); } d->labelVideoDuration->setAdjustedText(durationString); } void ItemPropertiesTab::setVideoFrameRate(const QString& str) { // use string given as parameter by default because it contains the value for "unavailable" if needed QString frameRateString = str; bool ok; const double frameRateDouble = str.toDouble(&ok); if (ok) { frameRateString = QLocale().toString(frameRateDouble) + i18n(" fps"); } d->labelVideoFrameRate->setAdjustedText(frameRateString); } void ItemPropertiesTab::setVideoVideoCodec(const QString& str) { d->labelVideoVideoCodec->setAdjustedText(str); } void ItemPropertiesTab::setTags(const QStringList& tagPaths, const QStringList& tagNames) { Q_UNUSED(tagNames); d->labelTags->setAdjustedText(shortenedTagPaths(tagPaths).join(QLatin1Char('\n'))); } typedef QPair PathValuePair; static bool naturalLessThan(const PathValuePair& a, const PathValuePair& b) { return (QCollator().compare(a.first, b.first) < 0); } QStringList ItemPropertiesTab::shortenedTagPaths(const QStringList& tagPaths, QList* identifiers) { QList tagsSorted; if (identifiers) { for (int i = 0 ; i < tagPaths.size() ; ++i) { tagsSorted << PathValuePair(tagPaths.at(i), (*identifiers).at(i)); } } else { for (int i = 0 ; i < tagPaths.size() ; ++i) { tagsSorted << PathValuePair(tagPaths.at(i), QVariant()); } } std::stable_sort(tagsSorted.begin(), tagsSorted.end(), naturalLessThan); if (identifiers) { identifiers->clear(); } QStringList tagsShortened; QString previous; - foreach(const PathValuePair& pair, tagsSorted) + foreach (const PathValuePair& pair, tagsSorted) { const QString& tagPath = pair.first; QString shortenedPath = tagPath; QStringList currentPath = tagPath.split(QLatin1Char('/'), QString::SkipEmptyParts); QStringList previousPath = previous.split(QLatin1Char('/'), QString::SkipEmptyParts); int depth; for (depth = 0 ; depth < currentPath.size() && depth < previousPath.size() ; ++depth) { if (currentPath.at(depth) != previousPath.at(depth)) break; } if (depth) { QString indent; indent.fill(QLatin1Char(' '), qMin(depth, 5)); //indent += QChar(0x2026); shortenedPath = indent + tagPath.section(QLatin1Char('/'), depth); } shortenedPath.replace(QLatin1Char('/'), QLatin1String(" / ")); tagsShortened << shortenedPath; previous = tagPath; if (identifiers) { (*identifiers) << pair.second; } } return tagsShortened; } void ItemPropertiesTab::shortenedMakeInfo(QString& make) { make.remove(QLatin1String(" CORPORATION"), Qt::CaseInsensitive); // from Nikon, Pentax, and Olympus make.remove(QLatin1String("EASTMAN "), Qt::CaseInsensitive); // from Kodak make.remove(QLatin1String(" COMPANY"), Qt::CaseInsensitive); // from Kodak make.remove(QLatin1String(" OPTICAL CO.,LTD"), Qt::CaseInsensitive); // from Olympus make.remove(QLatin1String(" IMAGING CORP."), Qt::CaseInsensitive); // from Olympus make.remove(QLatin1String(" Techwin co.,Ltd."), Qt::CaseInsensitive); // from Samsung make.remove(QLatin1String(" Co.,Ltd."), Qt::CaseInsensitive); // from Minolta make.remove(QLatin1String(" Electric Co.,Ltd."), Qt::CaseInsensitive); // from Sanyo make.remove(QLatin1String(" Electric Co.,Ltd"), Qt::CaseInsensitive); // from Sanyo } void ItemPropertiesTab::shortenedModelInfo(QString& model) { model.remove(QLatin1String("Canon "), Qt::CaseInsensitive); model.remove(QLatin1String("NIKON "), Qt::CaseInsensitive); model.remove(QLatin1String("PENTAX "), Qt::CaseInsensitive); model.remove(QLatin1String(" DIGITAL"), Qt::CaseInsensitive); // from Canon model.remove(QLatin1String("KODAK "), Qt::CaseInsensitive); model.remove(QLatin1String(" CAMERA"), Qt::CaseInsensitive); // from Kodak } /** * Find rational approximation to given real number * * val : double value to convert as humain readable fraction * num : fraction numerator * den : fraction denominator * maxden : the maximum denominator allowed * * This function return approximation error of the fraction * * Based on the theory of continued fractions * if x = a1 + 1/(a2 + 1/(a3 + 1/(a4 + ...))) * Then best approximation is found by truncating this series * wwith some adjustments in the last term. * * Note the fraction can be recovered as the first column of the matrix * ( a1 1 ) ( a2 1 ) ( a3 1 ) ... * ( 1 0 ) ( 1 0 ) ( 1 0 ) * Instead of keeping the sequence of continued fraction terms, * we just keep the last partial product of these matrices. * * Details: http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions * */ double ItemPropertiesTab::doubleToHumanReadableFraction(double val, long* num, long* den, long maxden) { double x = val; long m[2][2]; long ai; // Initialize matrix m[0][0] = m[1][1] = 1; m[0][1] = m[1][0] = 0; // Loop finding terms until denominator gets too big while (m[1][0] * (ai = (long)x) + m[1][1] <= maxden) { long t = m[0][0] * ai + m[0][1]; m[0][1] = m[0][0]; m[0][0] = t; t = m[1][0] * ai + m[1][1]; m[1][1] = m[1][0]; m[1][0] = t; if (x == (double)ai) { break; // division by zero } x = 1 / (x - (double)ai); if (x > (double)0x7FFFFFFF) { break; // representation failure } } // Now remaining x is between 0 and 1/ai // Approx as either 0 or 1/m where m is max that will fit in maxden *num = m[0][0]; *den = m[1][0]; // Return approximation error return (val - ((double)m[0][0] / (double)m[1][0])); } bool ItemPropertiesTab::aspectRatioToString(int width, int height, QString& arString) { if ((width == 0) || (height == 0)) { return false; } double ratio = (double)qMax(width, height) / (double)qMin(width, height); long num = 0; long den = 0; doubleToHumanReadableFraction(ratio, &num, &den, 10); double aratio = (double)qMax(num, den) / (double)qMin(num, den); arString = i18nc("width : height (Aspect Ratio)", "%1:%2 (%3)", (width > height) ? num : den, (width > height) ? den : num, QLocale().toString(aratio, 'g', 2)); return true; } QString ItemPropertiesTab::permissionsString(const QFileInfo& fi) { QString str; QFile::Permissions perms = fi.permissions(); str.append(fi.isSymLink() ? QLatin1String("l") : QLatin1String("-")); str.append((perms & QFileDevice::ReadOwner) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteOwner) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeOwner) ? QLatin1String("x") : QLatin1String("-")); str.append((perms & QFileDevice::ReadGroup) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteGroup) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeGroup) ? QLatin1String("x") : QLatin1String("-")); str.append((perms & QFileDevice::ReadOther) ? QLatin1String("r") : QLatin1String("-")); str.append((perms & QFileDevice::WriteOther) ? QLatin1String("w") : QLatin1String("-")); str.append((perms & QFileDevice::ExeOther) ? QLatin1String("x") : QLatin1String("-")); return str; } QString ItemPropertiesTab::humanReadableBytesCount(qint64 bytes, bool si) { int unit = si ? 1000 : 1024; QString byteStr = i18nc("unit file size in bytes", "B"); QString ret = QString::number(bytes); if (bytes >= unit) { int exp = (int)(qLn(bytes) / qLn(unit)); QString pre = QString(si ? QLatin1String("kMGTPEZY") : QLatin1String("KMGTPEZY")).at(exp-1) + (si ? QLatin1String("") : QLatin1String("i")); ret.sprintf("%.1f %s", bytes / qPow(unit, exp), pre.toLatin1().constData()); } return (QString::fromUtf8("%1%2").arg(ret).arg(byteStr)); } } // namespace Digikam diff --git a/core/libs/settings/applicationsettings_mime.cpp b/core/libs/settings/applicationsettings_mime.cpp index 4de658953d..14c6ea9992 100644 --- a/core/libs/settings/applicationsettings_mime.cpp +++ b/core/libs/settings/applicationsettings_mime.cpp @@ -1,136 +1,136 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-16-10 * Description : application settings interface * * Copyright (C) 2003-2004 by Renchi Raju * Copyright (C) 2003-2019 by Gilles Caulier * Copyright (C) 2007 by Arnd Baecker * Copyright (C) 2014 by Mohamed_Anwer * Copyright (C) 2014 by Veaceslav Munteanu * * 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, 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. * * ============================================================ */ // Local includes #include "drawdecoder.h" #include "applicationsettings.h" #include "applicationsettings_p.h" #include "coredbaccess.h" #include "coredb.h" namespace Digikam { QString ApplicationSettings::getImageFileFilter() const { QStringList imageSettings; CoreDbAccess().db()->getFilterSettings(&imageSettings, nullptr, nullptr); QStringList wildcards; - foreach(const QString& suffix, imageSettings) + foreach (const QString& suffix, imageSettings) { wildcards << QLatin1String("*.") + suffix; } return wildcards.join(QLatin1Char(' ')); } QString ApplicationSettings::getMovieFileFilter() const { QStringList movieSettings; CoreDbAccess().db()->getFilterSettings(nullptr, &movieSettings, nullptr); QStringList wildcards; - foreach(const QString& suffix, movieSettings) + foreach (const QString& suffix, movieSettings) { wildcards << QLatin1String("*.") + suffix; } return wildcards.join(QLatin1Char(' ')); } QString ApplicationSettings::getAudioFileFilter() const { QStringList audioSettings; CoreDbAccess().db()->getFilterSettings(nullptr, nullptr, &audioSettings); QStringList wildcards; - foreach(const QString& suffix, audioSettings) + foreach (const QString& suffix, audioSettings) { wildcards << QLatin1String("*.") + suffix; } return wildcards.join(QLatin1Char(' ')); } QString ApplicationSettings::getRawFileFilter() const { QStringList supportedRaws = DRawDecoder::rawFilesList(); QStringList imageSettings; CoreDbAccess().db()->getFilterSettings(&imageSettings, nullptr, nullptr); // form intersection: those extensions that are supported as RAW as well in the list of allowed extensions for (QStringList::iterator it = supportedRaws.begin(); it != supportedRaws.end(); ) { if (imageSettings.contains(*it)) { ++it; } else { it = supportedRaws.erase(it); } } QStringList wildcards; - foreach(const QString& suffix, supportedRaws) + foreach (const QString& suffix, supportedRaws) { wildcards << QLatin1String("*.") + suffix; } return wildcards.join(QLatin1Char(' ')); } QString ApplicationSettings::getAllFileFilter() const { QStringList imageFilter, audioFilter, videoFilter; CoreDbAccess().db()->getFilterSettings(&imageFilter, &videoFilter, &audioFilter); QStringList wildcards; - foreach(const QString& suffix, imageFilter) + foreach (const QString& suffix, imageFilter) { wildcards << QLatin1String("*.") + suffix; } - foreach(const QString& suffix, audioFilter) + foreach (const QString& suffix, audioFilter) { wildcards << QLatin1String("*.") + suffix; } - foreach(const QString& suffix, videoFilter) + foreach (const QString& suffix, videoFilter) { wildcards << QLatin1String("*.") + suffix; } return wildcards.join(QLatin1Char(' ')); } } // namespace Digikam diff --git a/core/libs/tags/engine/tagsactionmngr.cpp b/core/libs/tags/engine/tagsactionmngr.cpp index 5ad72a69fb..8dda270c24 100644 --- a/core/libs/tags/engine/tagsactionmngr.cpp +++ b/core/libs/tags/engine/tagsactionmngr.cpp @@ -1,537 +1,537 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-24 * Description : Tags Action Manager * * Copyright (C) 2011-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "tagsactionmngr.h" // Qt includes #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "coredbaccess.h" #include "coredbconstants.h" #include "coredbwatch.h" #include "coredbinfocontainers.h" #include "digikamapp.h" #include "dxmlguiwindow.h" #include "itemiconview.h" #include "imagewindow.h" #include "lighttablewindow.h" #include "picklabelwidget.h" #include "colorlabelwidget.h" #include "tagscache.h" #include "tagproperties.h" #include "ratingwidget.h" #include "slideshow.h" #include "syncjob.h" namespace Digikam { TagsActionMngr* TagsActionMngr::m_defaultManager = nullptr; TagsActionMngr* TagsActionMngr::defaultManager() { return m_defaultManager; } class Q_DECL_HIDDEN TagsActionMngr::Private { public: explicit Private() : ratingShortcutPrefix(QLatin1String("rateshortcut")), tagShortcutPrefix(QLatin1String("tagshortcut")), pickShortcutPrefix(QLatin1String("pickshortcut")), colorShortcutPrefix(QLatin1String("colorshortcut")) { } QMultiMap tagsActionMap; QList actionCollectionList; const QString ratingShortcutPrefix; const QString tagShortcutPrefix; const QString pickShortcutPrefix; const QString colorShortcutPrefix; }; // ------------------------------------------------------------------------------------------------- TagsActionMngr::TagsActionMngr(QWidget* const parent) : QObject(parent), d(new Private) { if (!m_defaultManager) { m_defaultManager = this; } connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotAlbumDeleted(Album*))); connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)), this, SLOT(slotImageTagChanged(ImageTagChangeset))); } TagsActionMngr::~TagsActionMngr() { delete d; if (m_defaultManager == this) { m_defaultManager = nullptr; } } QString TagsActionMngr::ratingShortcutPrefix() const { return d->ratingShortcutPrefix; } QString TagsActionMngr::tagShortcutPrefix() const { return d->tagShortcutPrefix; } QString TagsActionMngr::pickShortcutPrefix() const { return d->pickShortcutPrefix; } QString TagsActionMngr::colorShortcutPrefix() const { return d->colorShortcutPrefix; } void TagsActionMngr::registerTagsActionCollections() { d->actionCollectionList.append(DigikamApp::instance()->actionCollection()); d->actionCollectionList.append(ImageWindow::imageWindow()->actionCollection()); d->actionCollectionList.append(LightTableWindow::lightTableWindow()->actionCollection()); // Create Tags shortcuts. QList tagIds = TagsCache::instance()->tagsWithProperty(TagPropertyName::tagKeyboardShortcut()); - foreach(int tagId, tagIds) + foreach (int tagId, tagIds) { createTagActionShortcut(tagId); } } QList TagsActionMngr::actionCollections() const { return d->actionCollectionList; } void TagsActionMngr::registerLabelsActions(KActionCollection* const ac) { // Create Rating shortcuts. for (int i = RatingMin ; i <= RatingMax ; ++i) { createRatingActionShortcut(ac, i); } // Create Color Label shortcuts. for (int i = NoColorLabel ; i <= WhiteLabel ; ++i) { createColorLabelActionShortcut(ac, i); } // Create Pick Label shortcuts. for (int i = NoPickLabel ; i <= AcceptedLabel ; ++i) { createPickLabelActionShortcut(ac, i); } } void TagsActionMngr::registerActionsToWidget(QWidget* const wdg) { DXmlGuiWindow* const win = dynamic_cast(qApp->activeWindow()); if (win) { - foreach(QAction* const ac, win->actionCollection()->actions()) + foreach (QAction* const ac, win->actionCollection()->actions()) { if (ac->objectName().startsWith(d->ratingShortcutPrefix) || ac->objectName().startsWith(d->tagShortcutPrefix) || ac->objectName().startsWith(d->pickShortcutPrefix) || ac->objectName().startsWith(d->colorShortcutPrefix)) { wdg->addAction(ac); } } } } bool TagsActionMngr::createRatingActionShortcut(KActionCollection* const ac, int rating) { if (ac) { QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->ratingShortcutPrefix).arg(rating)); action->setText(i18n("Assign Rating \"%1 Star\"", rating)); ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("CTRL+%1").arg(rating))); action->setIcon(RatingWidget::buildIcon(rating, 32)); action->setData(rating); connect(action, SIGNAL(triggered()), this, SLOT(slotAssignFromShortcut())); return true; } return false; } bool TagsActionMngr::createPickLabelActionShortcut(KActionCollection* const ac, int pickId) { if (ac) { QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->pickShortcutPrefix).arg(pickId)); action->setText(i18n("Assign Pick Label \"%1\"", PickLabelWidget::labelPickName((PickLabel)pickId))); ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+%1").arg(pickId))); action->setIcon(PickLabelWidget::buildIcon((PickLabel)pickId)); action->setData(pickId); connect(action, SIGNAL(triggered()), this, SLOT(slotAssignFromShortcut())); return true; } return false; } bool TagsActionMngr::createColorLabelActionShortcut(KActionCollection* const ac, int colorId) { if (ac) { QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->colorShortcutPrefix).arg(colorId)); action->setText(i18n("Assign Color Label \"%1\"", ColorLabelWidget::labelColorName((ColorLabel)colorId))); ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+CTRL+%1").arg(colorId))); action->setIcon(ColorLabelWidget::buildIcon((ColorLabel)colorId, 32)); action->setData(colorId); connect(action, SIGNAL(triggered()), this, SLOT(slotAssignFromShortcut())); return true; } return false; } bool TagsActionMngr::createTagActionShortcut(int tagId) { if (!tagId) { return false; } TAlbum* const talbum = AlbumManager::instance()->findTAlbum(tagId); if (!talbum) { return false; } QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut()); if (value.isEmpty()) { return false; } QKeySequence ks(value); // FIXME: tag icons can be files on disk, or system icon names. Only the latter will work here. QIcon icon(SyncJob::getTagThumbnail(talbum)); qCDebug(DIGIKAM_GENERAL_LOG) << "Create Shortcut " << ks.toString() << " to Tag " << talbum->title() << " (" << tagId << ")"; - foreach(KActionCollection* const ac, d->actionCollectionList) + foreach (KActionCollection* const ac, d->actionCollectionList) { QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->tagShortcutPrefix).arg(tagId)); action->setText(i18n("Assign Tag \"%1\"", talbum->title())); action->setParent(this); ac->setDefaultShortcut(action, ks); action->setIcon(icon); action->setData(tagId); connect(action, SIGNAL(triggered()), this, SLOT(slotAssignFromShortcut())); connect(action, SIGNAL(changed()), this, SLOT(slotTagActionChanged())); d->tagsActionMap.insert(tagId, action); } return true; } void TagsActionMngr::slotTagActionChanged() { QAction* const action = dynamic_cast(sender()); if (!action) { return; } int tagId = action->data().toInt(); QKeySequence ks; QStringList lst = action->shortcut().toString().split(QLatin1Char(',')); if (!lst.isEmpty()) ks = QKeySequence(lst.first()); updateTagShortcut(tagId, ks); } void TagsActionMngr::updateTagShortcut(int tagId, const QKeySequence& ks) { if (!tagId) { return; } qCDebug(DIGIKAM_GENERAL_LOG) << "Tag Shortcut " << tagId << "Changed to " << ks; QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut()); if (value == ks.toString()) { return; } TagProperties tprop(tagId); if (ks.isEmpty()) { removeTagActionShortcut(tagId); tprop.removeProperties(TagPropertyName::tagKeyboardShortcut()); } else { removeTagActionShortcut(tagId); tprop.setProperty(TagPropertyName::tagKeyboardShortcut(), ks.toString()); createTagActionShortcut(tagId); } } void TagsActionMngr::slotAlbumDeleted(Album* album) { TAlbum* const talbum = dynamic_cast(album); if (!talbum) { return; } removeTagActionShortcut(talbum->id()); qCDebug(DIGIKAM_GENERAL_LOG) << "Delete Shortcut assigned to tag " << album->id(); } bool TagsActionMngr::removeTagActionShortcut(int tagId) { if (!d->tagsActionMap.contains(tagId)) { return false; } - foreach(QAction* const act, d->tagsActionMap.values(tagId)) + foreach (QAction* const act, d->tagsActionMap.values(tagId)) { if (act) { KActionCollection* const ac = dynamic_cast(act->parent()); if (ac) { ac->takeAction(act); } delete act; } } d->tagsActionMap.remove(tagId); return true; } void TagsActionMngr::slotAssignFromShortcut() { QAction* const action = dynamic_cast(sender()); if (!action) { return; } int val = action->data().toInt(); qCDebug(DIGIKAM_GENERAL_LOG) << "Shortcut value: " << val; QWidget* const w = qApp->activeWindow(); DigikamApp* const dkw = dynamic_cast(w); if (dkw) { //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by DigikamApp"; if (action->objectName().startsWith(d->ratingShortcutPrefix)) { dkw->view()->slotAssignRating(val); } else if (action->objectName().startsWith(d->pickShortcutPrefix)) { dkw->view()->slotAssignPickLabel(val); } else if (action->objectName().startsWith(d->colorShortcutPrefix)) { dkw->view()->slotAssignColorLabel(val); } else if (action->objectName().startsWith(d->tagShortcutPrefix)) { dkw->view()->toggleTag(val); } return; } ImageWindow* const imw = dynamic_cast(w); if (imw) { //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by ImageWindow"; if (action->objectName().startsWith(d->ratingShortcutPrefix)) { imw->slotAssignRating(val); } else if (action->objectName().startsWith(d->pickShortcutPrefix)) { imw->slotAssignPickLabel(val); } else if (action->objectName().startsWith(d->colorShortcutPrefix)) { imw->slotAssignColorLabel(val); } else if (action->objectName().startsWith(d->tagShortcutPrefix)) { imw->toggleTag(val); } return; } LightTableWindow* const ltw = dynamic_cast(w); if (ltw) { //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by LightTableWindow"; if (action->objectName().startsWith(d->ratingShortcutPrefix)) { ltw->slotAssignRating(val); } else if (action->objectName().startsWith(d->pickShortcutPrefix)) { ltw->slotAssignPickLabel(val); } else if (action->objectName().startsWith(d->colorShortcutPrefix)) { ltw->slotAssignColorLabel(val); } else if (action->objectName().startsWith(d->tagShortcutPrefix)) { ltw->toggleTag(val); } return; } SlideShow* const sld = dynamic_cast(w); if (sld) { //qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by SlideShow"; if (action->objectName().startsWith(d->ratingShortcutPrefix)) { sld->slotAssignRating(val); } else if (action->objectName().startsWith(d->pickShortcutPrefix)) { sld->slotAssignPickLabel(val); } else if (action->objectName().startsWith(d->colorShortcutPrefix)) { sld->slotAssignColorLabel(val); } else if (action->objectName().startsWith(d->tagShortcutPrefix)) { sld->toggleTag(val); } return; } } // Special case with Slideshow which do not depend on database. void TagsActionMngr::slotImageTagChanged(const ImageTagChangeset&) { QWidget* const w = qApp->activeWindow(); SlideShow* const sld = dynamic_cast(w); if (sld) { QUrl url = sld->currentItem(); ItemInfo info = ItemInfo::fromUrl(url); sld->updateTags(url, AlbumManager::instance()->tagNames(info.tagIds())); } } } // namespace Digikam diff --git a/core/libs/tags/widgets/tagcheckview.cpp b/core/libs/tags/widgets/tagcheckview.cpp index b15230a7dd..8b35f3df7c 100644 --- a/core/libs/tags/widgets/tagcheckview.cpp +++ b/core/libs/tags/widgets/tagcheckview.cpp @@ -1,274 +1,274 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-05-05 * Description : tags filter view * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2009-2010 by Andi Clemens * Copyright (C) 2009-2010 by Johannes Wienke * * 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, 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. * * ============================================================ */ #include "tagcheckview.h" // Qt includes #include #include // KDE includes #include #include #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagmodificationhelper.h" namespace Digikam { class Q_DECL_HIDDEN TagCheckView::Private { public: explicit Private() : toggleAutoTags(TagCheckView::NoToggleAuto), checkNewTags(false), toggleAutoAction(nullptr), toggleNoneAction(nullptr), toggleChildrenAction(nullptr), toggleParentsAction(nullptr), toggleBothAction(nullptr) { } static const QString configToggleAutoTagsEntry; TagCheckView::ToggleAutoTags toggleAutoTags; bool checkNewTags; KSelectAction* toggleAutoAction; QAction* toggleNoneAction; QAction* toggleChildrenAction; QAction* toggleParentsAction; QAction* toggleBothAction; }; const QString TagCheckView::Private::configToggleAutoTagsEntry(QLatin1String("Toggle Auto Tags")); // -------------------------------------------------------- TagCheckView::TagCheckView(QWidget* const parent, TagModel* const tagModel) : TagFolderView(parent, tagModel), d(new Private) { setSelectAlbumOnClick(false); setExpandOnSingleClick(false); setSelectOnContextMenu(false); setShowFindDuplicateAction(false); // prepare custom menu action d->toggleAutoAction = new KSelectAction(i18n("Toggle Auto"), this); d->toggleNoneAction = d->toggleAutoAction->addAction(i18nc("no auto toggle", "None")); d->toggleAutoAction->menu()->addSeparator(); d->toggleChildrenAction = d->toggleAutoAction->addAction(i18nc("toggle child tags", "Children")); d->toggleParentsAction = d->toggleAutoAction->addAction(i18nc("toggle parent tag", "Parents")); d->toggleBothAction = d->toggleAutoAction->addAction(i18nc("toggle child and parent tags", "Both")); d->toggleNoneAction->setData(NoToggleAuto); d->toggleChildrenAction->setData(Children); d->toggleParentsAction->setData(Parents); d->toggleBothAction->setData(ChildrenAndParents); connect(d->toggleAutoAction, SIGNAL(triggered(QAction*)), this, SLOT(toggleAutoActionSelected(QAction*))); connect(albumModel(), SIGNAL(checkStateChanged(Album*,Qt::CheckState)), this, SLOT(slotCheckStateChange(Album*,Qt::CheckState))); } TagCheckView::~TagCheckView() { delete d; } void TagCheckView::slotResetCheckState() { albumModel()->resetAllCheckedAlbums(); } void TagCheckView::slotCheckStateChange(Album* album, Qt::CheckState state) { Q_UNUSED(album); Q_UNUSED(state); // handle custom toggle modes disconnect(albumModel(), SIGNAL(checkStateChanged(Album*,Qt::CheckState)), this, SLOT(slotCheckStateChange(Album*,Qt::CheckState))); // avoid signal recursion here switch (d->toggleAutoTags) { case Children: albumModel()->setCheckStateForChildren(album, state); break; case Parents: albumModel()->setCheckStateForParents(album, state); break; case ChildrenAndParents: albumModel()->setCheckStateForChildren(album, state); albumModel()->setCheckStateForParents(album, state); break; default: break; } connect(albumModel(), SIGNAL(checkStateChanged(Album*,Qt::CheckState)), this, SLOT(slotCheckStateChange(Album*,Qt::CheckState))); emit checkedTagsChanged(getCheckedTags(), getPartiallyCheckedTags()); } void TagCheckView::doLoadState() { TagFolderView::doLoadState(); KConfigGroup group = getConfigGroup(); d->toggleAutoTags = (ToggleAutoTags) (group.readEntry(entryName(d->configToggleAutoTagsEntry), (int)NoToggleAuto)); } void TagCheckView::doSaveState() { TagFolderView::doSaveState(); KConfigGroup group = getConfigGroup(); group.writeEntry(entryName(d->configToggleAutoTagsEntry), (int)(d->toggleAutoTags)); group.sync(); } QList TagCheckView::getCheckedTags() const { QList tags; - foreach(Album* const album, albumModel()->checkedAlbums()) + foreach (Album* const album, albumModel()->checkedAlbums()) { TAlbum* const tag = dynamic_cast (album); if (tag) { tags << tag; } } return tags; } QList TagCheckView::getPartiallyCheckedTags() const { QList tags; - foreach(Album* const album, albumModel()->partiallyCheckedAlbums()) + foreach (Album* const album, albumModel()->partiallyCheckedAlbums()) { TAlbum* const tag = dynamic_cast (album); if (tag) { tags << tag; } } return tags; } TagCheckView::ToggleAutoTags TagCheckView::getToggleAutoTags() const { return d->toggleAutoTags; } void TagCheckView::setToggleAutoTags(TagCheckView::ToggleAutoTags toggle) { d->toggleAutoTags = toggle; } void TagCheckView::setCheckNewTags(bool checkNewTags) { if (d->checkNewTags == checkNewTags) { return; } d->checkNewTags = checkNewTags; if (d->checkNewTags) { connect(tagModificationHelper(), SIGNAL(tagCreated(TAlbum*)), this, SLOT(slotCreatedNewTagByContextMenu(TAlbum*))); } else { disconnect(tagModificationHelper(), SIGNAL(tagCreated(TAlbum*)), this, SLOT(slotCreatedNewTagByContextMenu(TAlbum*))); } } bool TagCheckView::checkNewTags() const { return d->checkNewTags; } void TagCheckView::slotCreatedNewTagByContextMenu(TAlbum* tag) { albumModel()->setChecked(tag, true); } void TagCheckView::addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album) { TagFolderView::addCustomContextMenuActions(cmh, album); cmh.addSeparator(); // selection (checked) modification cmh.setAlbumModel(albumModel()); cmh.addAlbumCheckUncheckActions(album); cmh.addSeparator(); // automatic toggle cmh.addAction(d->toggleAutoAction); - foreach(QAction* const action, d->toggleAutoAction->actions()) + foreach (QAction* const action, d->toggleAutoAction->actions()) { if (action->data().toInt() == d->toggleAutoTags) { action->setChecked(true); } } } void TagCheckView::toggleAutoActionSelected(QAction* action) { d->toggleAutoTags = static_cast(action->data().toInt()); } } // namespace Digikam diff --git a/core/libs/tags/widgets/tagspopupmenu.cpp b/core/libs/tags/widgets/tagspopupmenu.cpp index 0624ea5ade..5794494614 100644 --- a/core/libs/tags/widgets/tagspopupmenu.cpp +++ b/core/libs/tags/widgets/tagspopupmenu.cpp @@ -1,853 +1,853 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-09-07 * Description : a pop-up menu implementation to display a * hierarchical view of digiKam tags. * * Copyright (C) 2004 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * Copyright (C) 2006-2011 by Marcel Wiesweg * * Parts of the drawing code are inspired by from Trolltech ASA implementation. * * 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, 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. * * ============================================================ */ #include "tagspopupmenu.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "albumthumbnailloader.h" #include "tageditdlg.h" #include "tagscache.h" namespace Digikam { class Q_DECL_HIDDEN TagToggleAction : public QWidgetAction { public: TagToggleAction(const QString& text, QObject* const parent); TagToggleAction(const QIcon& icon, const QString& text, QObject* const parent); virtual QWidget* createWidget(QWidget* parent); void setSpecialChecked(bool checked); bool isChecked() const; void setCheckBoxHidden(bool hidden); bool isCheckBoxHidden() const; private: bool m_checked; bool m_checkBoxHidden; }; // ------------------------------------------------------------------------ class Q_DECL_HIDDEN TagToggleMenuWidget : public QWidget { public: TagToggleMenuWidget(QMenu* const parent, TagToggleAction* const action); protected: virtual QSize sizeHint() const; virtual void paintEvent(QPaintEvent*); private: void initMenuStyleOption(QStyleOptionMenuItem* option) const; void initViewStyleOption(QStyleOptionViewItem* option) const; QSize menuItemSize(QStyleOptionMenuItem* opt) const; QRect checkIndicatorSize(QStyleOption* option) const; private: QMenu* m_menu; TagToggleAction* m_action; }; // ------------------------------------------------------------------------ TagToggleMenuWidget::TagToggleMenuWidget(QMenu* const parent, TagToggleAction* const action) : QWidget(parent) { m_menu = parent; m_action = action; setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, nullptr, this)); } QSize TagToggleMenuWidget::sizeHint() const { // init style option for menu item QStyleOptionMenuItem opt; initMenuStyleOption(&opt); // get the individual sizes QSize menuSize = menuItemSize(&opt); QRect checkRect = checkIndicatorSize(&opt); const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this) + 1; // return widget size int width = margin + checkRect.width() + menuSize.width() + margin; QSize size(width, qMax(checkRect.height(), menuSize.height())); return size; } void TagToggleMenuWidget::paintEvent(QPaintEvent*) { // init style option for menu item QStyleOptionMenuItem menuOpt; initMenuStyleOption(&menuOpt); // init style option for check indicator QStyleOptionViewItem viewOpt; initViewStyleOption(&viewOpt); // get a suitable margin const int margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this); const int frameMargin = style()->pixelMetric(QStyle::PM_MenuDesktopFrameWidth, nullptr, this); // create painter QPainter p(this); // the menu rect should not go beyond the parent menu in width // move by margin and free room for menu frame if (menuOpt.direction == Qt::RightToLeft) { // right-to-left untested viewOpt.rect.translate(-margin, 0); menuOpt.rect.translate(-margin, 0); menuOpt.menuRect.adjust(margin, 0, 0, 0); } else { viewOpt.rect.translate(margin, 0); menuOpt.rect.translate(margin, 0); menuOpt.menuRect.adjust(0, 0, -margin, 0); } // clear the background of the check indicator QStyleOptionMenuItem clearOpt(menuOpt); clearOpt.state = QStyle::State_None; clearOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; clearOpt.checkType = QStyleOptionMenuItem::NotCheckable; clearOpt.rect = viewOpt.rect; style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this); // draw a check indicator like the one used in a treeview QRect checkRect = checkIndicatorSize(&menuOpt); viewOpt.rect = checkRect; if (!m_action->isCheckBoxHidden()) { style()->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &viewOpt, &p, this); } // move by size of check indicator if (menuOpt.direction == Qt::RightToLeft) { menuOpt.rect.translate( - checkRect.width() - margin, 0); menuOpt.rect.adjust( checkRect.width() + margin, 0, 0, 0); } else { menuOpt.rect.translate(checkRect.right() + margin, 0); menuOpt.rect.adjust(0, 0, - checkRect.right() - margin, 0); } // draw a full menu item - icon, text and menu indicator style()->drawControl(QStyle::CE_MenuItem, &menuOpt, &p, this); // draw the frame on the right if (frameMargin) { QRegion borderReg; borderReg += QRect(width() - frameMargin, 0, frameMargin, height()); // right p.setClipRegion(borderReg); QStyleOptionFrame frame; frame.rect = rect(); frame.palette = palette(); frame.state = QStyle::State_None; frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth); frame.midLineWidth = 0; style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this); } } void TagToggleMenuWidget::initMenuStyleOption(QStyleOptionMenuItem* option) const { // set basic option from widget properties option->initFrom(this); // set menu item state option->state = QStyle::State_None; option->state |= QStyle::State_Enabled; if (m_menu->activeAction() == m_action) // if hovered etc. { option->state |= QStyle::State_Selected; } // if (mouseDown) option->state |= QStyle::State_Sunken; // We have a special case here: menu items which are checked are not selectable, // it is an "Assign Tags" menu. To signal this, we change the pallette. // But only if there is no submenu... if (m_action->isChecked() && !m_action->menu()) { option->palette.setCurrentColorGroup(QPalette::Disabled); option->state &= ~QStyle::State_Enabled; } // set options from m_action option->font = m_action->font(); option->icon = m_action->icon(); option->text = m_action->text(); // we do the check mark ourselves option->checked = false; option->menuHasCheckableItems = false; option->checkType = QStyleOptionMenuItem::NotCheckable; // Don't forget the submenu indicator if (m_action->menu()) { option->menuItemType = QStyleOptionMenuItem::SubMenu; } else { option->menuItemType = QStyleOptionMenuItem::Normal; } // seems QMenu does it like this option->maxIconWidth = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this); option->rect = rect(); option->menuRect = parentWidget()->rect(); } void TagToggleMenuWidget::initViewStyleOption(QStyleOptionViewItem* option) const { // set basic option from widget properties option->initFrom(this); // set check state if (m_action->isChecked()) { option->state |= QStyle::State_On; } else { option->state |= QStyle::State_Off; } } QSize TagToggleMenuWidget::menuItemSize(QStyleOptionMenuItem* opt) const { QSize size; QFontMetrics fm(fontMetrics()); #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) size.setWidth(fm.horizontalAdvance(m_action->text())); #else size.setWidth(fm.width(m_action->text())); #endif size.setHeight(fm.height()); if (!m_action->icon().isNull()) { if (size.height() < opt->maxIconWidth) { size.setHeight(opt->maxIconWidth); } } return style()->sizeFromContents(QStyle::CT_MenuItem, opt, size, this); } QRect TagToggleMenuWidget::checkIndicatorSize(QStyleOption* option) const { if (m_action->isCheckBoxHidden()) { return QRect(); } QStyleOptionButton opt; opt.QStyleOption::operator=(*option); //opt.rect = bounding; return style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt, this); } // ------------------------------------------------------------------------ TagToggleAction::TagToggleAction(const QString& text, QObject* const parent) : QWidgetAction(parent), m_checked(false), m_checkBoxHidden(false) { setText(text); setCheckable(true); } TagToggleAction::TagToggleAction(const QIcon& icon, const QString& text, QObject* const parent) : QWidgetAction(parent), m_checked(false), m_checkBoxHidden(false) { setIcon(icon); setText(text); setCheckable(true); } QWidget* TagToggleAction::createWidget(QWidget* parent) { QMenu* const menu = qobject_cast(parent); if (menu) { return (new TagToggleMenuWidget(menu, this)); } else { return nullptr; } } void TagToggleAction::setSpecialChecked(bool checked) { // something is resetting the checked property when there is a submenu. // Use this to store "checked" anyway. // Note: the method isChecked() is not virtual. m_checked = checked; setChecked(checked); } bool TagToggleAction::isChecked() const { return m_checked || QWidgetAction::isChecked(); } void TagToggleAction::setCheckBoxHidden(bool hidden) { m_checkBoxHidden = hidden; } bool TagToggleAction::isCheckBoxHidden() const { return m_checkBoxHidden; } // ------------------------------------------------------------------------ class Q_DECL_HIDDEN TagsPopupMenu::Private { public: explicit Private() { addTagActions = nullptr; toggleTagActions = nullptr; mode = ASSIGN; } QPixmap addTagPix; QPixmap recentTagPix; QPixmap tagViewPix; QSet assignedTags; QSet parentAssignedTags; QList selectedImageIDs; QActionGroup* addTagActions; QActionGroup* toggleTagActions; TagsPopupMenu::Mode mode; }; TagsPopupMenu::TagsPopupMenu(qlonglong selectedImageId, Mode mode, QWidget* const parent) : QMenu(parent), d(new Private) { d->selectedImageIDs << selectedImageId; setup(mode); } TagsPopupMenu::TagsPopupMenu(const QList& selectedImageIds, Mode mode, QWidget* const parent) : QMenu(parent), d(new Private) { d->selectedImageIDs = selectedImageIds; setup(mode); } void TagsPopupMenu::setup(Mode mode) { d->mode = mode; d->addTagPix = QIcon::fromTheme(QLatin1String("tag")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->recentTagPix = QIcon::fromTheme(QLatin1String("tag-assigned")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->tagViewPix = QIcon::fromTheme(QLatin1String("edit-text-frame-update")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)); d->addTagActions = new QActionGroup(this); d->toggleTagActions = new QActionGroup(this); setSeparatorsCollapsible(true); connect(d->addTagActions, SIGNAL(triggered(QAction*)), this, SLOT(slotAddTag(QAction*))); connect(d->toggleTagActions, SIGNAL(triggered(QAction*)), this, SLOT(slotToggleTag(QAction*))); connect(this, SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); connect(loader, SIGNAL(signalThumbnail(Album*,QPixmap)), this, SLOT(slotTagThumbnail(Album*,QPixmap))); // we are not interested in signalThumbnailFailed } TagsPopupMenu::~TagsPopupMenu() { delete d; } void TagsPopupMenu::clearPopup() { d->assignedTags.clear(); d->parentAssignedTags.clear(); clear(); } void TagsPopupMenu::slotAboutToShow() { clearPopup(); AlbumManager* const man = AlbumManager::instance(); if (d->mode == REMOVE || d->mode == DISPLAY) { if (d->selectedImageIDs.isEmpty()) { menuAction()->setEnabled(false); return; } d->assignedTags = QSet::fromList(CoreDbAccess().db()->getItemCommonTagIDs(d->selectedImageIDs)); if (d->assignedTags.isEmpty()) { menuAction()->setEnabled(false); return; } // also add the parents of the assigned tags bool hasValidTag = false; for (QSet::const_iterator it = d->assignedTags.constBegin() ; it != d->assignedTags.constEnd() ; ++it) { TAlbum* const album = man->findTAlbum(*it); if (!album || album->isInternalTag()) { continue; } hasValidTag = true; Album* a = album->parent(); while (a) { d->parentAssignedTags << a->id(); a = a->parent(); } } if (!hasValidTag) { menuAction()->setEnabled(false); return; } } else if (d->mode == ASSIGN) { if (d->selectedImageIDs.count() == 1) { d->assignedTags = QSet::fromList(CoreDbAccess().db()->getItemCommonTagIDs(d->selectedImageIDs)); } } else if (d->mode == RECENTLYASSIGNED) { AlbumList recentTags = man->getRecentlyAssignedTags(); if (recentTags.isEmpty()) { addSection(d->recentTagPix, i18n("No Recently Assigned Tags")); } else { addSection(d->recentTagPix, i18n("Recently Assigned Tags")); for (AlbumList::const_iterator it = recentTags.constBegin() ; it != recentTags.constEnd() ; ++it) { TAlbum* const album = static_cast(*it); if (album) { TAlbum* const parent = dynamic_cast (album->parent()); if (parent) { QString p = parent->prettyUrl().section(QLatin1Char('/'), 1, -1); p.replace(QLatin1Char('/'), QLatin1String(" / ")); if (!p.isEmpty()) p = QLatin1String(" (") + p + QLatin1Char(')'); QString t = album->title() + p; t.replace(QLatin1Char('&'), QLatin1String("&&")); TagToggleAction* action = new TagToggleAction(t, d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); addAction(action); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Tag" << album << "do not have a valid parent"; } } } } } if ((d->mode == REMOVE || d->mode == DISPLAY) && d->assignedTags.count() < 10) { buildFlatMenu(this); } else { TAlbum* const album = man->findTAlbum(0); if (!album) { return; } iterateAndBuildMenu(this, album); if (d->mode == ASSIGN || d->mode == RECENTLYASSIGNED) { addSeparator(); TagToggleAction* const addTag = new TagToggleAction(d->addTagPix, i18n("Add New Tag..."), d->addTagActions); addTag->setData(0); // root id addTag->setCheckBoxHidden(true); addAction(addTag); addSeparator(); TagToggleAction* const moreTag = new TagToggleAction(d->tagViewPix, i18n("More Tags..."), d->addTagActions); moreTag->setData(-1); // special id to query tag view moreTag->setCheckBoxHidden(true); addAction(moreTag); } } } // for qSort bool lessThanByTitle(const Album* first, const Album* second) { return first->title() < second->title(); } void TagsPopupMenu::iterateAndBuildMenu(QMenu* menu, TAlbum* album) { QList sortedTags; for (Album* a = album->firstChild() ; a ; a = a->next()) { sortedTags << a; } std::stable_sort(sortedTags.begin(), sortedTags.end(), lessThanByTitle); for (QList::const_iterator it = sortedTags.constBegin() ; it != sortedTags.constEnd() ; ++it) { TAlbum* const a = (TAlbum*)(*it); if (a->isInternalTag()) { continue; } if (d->mode == RECENTLYASSIGNED) { continue; } else if (d->mode == REMOVE || d->mode == DISPLAY) { if (!d->assignedTags.contains(a->id()) && !d->parentAssignedTags.contains(a->id())) { continue; } } QString t = a->title(); t.replace(QLatin1Char('&'), QLatin1String("&&")); TagToggleAction* action = nullptr; if (d->mode == ASSIGN) { action = new TagToggleAction(t, d->toggleTagActions); if (d->assignedTags.contains(a->id())) { action->setSpecialChecked(true); } } else // REMOVE or DISPLAY mode { action = new TagToggleAction(t, d->toggleTagActions); action->setCheckBoxHidden(true); } action->setData(a->id()); menu->addAction(action); // get icon setAlbumIcon(action, a); if (a->firstChild()) { if ((d->mode != REMOVE && d->mode != DISPLAY) || d->parentAssignedTags.contains(a->id())) { action->setMenu(buildSubMenu(a->id())); } } } } QMenu* TagsPopupMenu::buildSubMenu(int tagid) { AlbumManager* const man = AlbumManager::instance(); TAlbum* const album = man->findTAlbum(tagid); if (!album) { return nullptr; } QMenu* const popup = new QMenu(this); popup->setSeparatorsCollapsible(true); if (d->mode == ASSIGN && !d->assignedTags.contains(album->id())) { TagToggleAction* const action = new TagToggleAction(i18n("Assign this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); } else if (d->mode == REMOVE && d->assignedTags.contains(tagid)) { TagToggleAction* const action = new TagToggleAction(i18n("Remove this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); d->toggleTagActions->addAction(action); } else if (d->mode == DISPLAY) { TagToggleAction* const action = new TagToggleAction(i18n("Go to this Tag"), d->toggleTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); setAlbumIcon(action, album); popup->addAction(action); popup->addSeparator(); d->toggleTagActions->addAction(action); } iterateAndBuildMenu(popup, album); if (d->mode == ASSIGN) { popup->addSeparator(); TagToggleAction* const action = new TagToggleAction(d->addTagPix, i18n("Add New Tag..."), d->addTagActions); action->setData(album->id()); action->setCheckBoxHidden(true); popup->addAction(action); } return popup; } void TagsPopupMenu::buildFlatMenu(QMenu* menu) { QList ids; QStringList shortenedPaths = TagsCache::instance()->shortenedTagPaths(d->assignedTags.toList(), &ids, TagsCache::NoLeadingSlash, TagsCache::NoHiddenTags); for (int i = 0 ; i < shortenedPaths.size() ; ++i) { QString t = shortenedPaths.at(i); t.replace(QLatin1Char('&'), QLatin1String("&&")); TAlbum* const a = AlbumManager::instance()->findTAlbum(ids.at(i)); if (!a) { continue; } TagToggleAction* const action = new TagToggleAction(t, d->toggleTagActions); if (d->mode == ASSIGN) { if (d->assignedTags.contains(a->id())) { action->setSpecialChecked(true); } } else // REMOVE or DISPLAY mode { action->setCheckBoxHidden(true); } action->setData(a->id()); menu->addAction(action); // get icon setAlbumIcon(action, a); } } void TagsPopupMenu::setAlbumIcon(QAction* action, TAlbum* album) { AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance(); QPixmap pix; if (!loader->getTagThumbnail(album, pix)) { if (pix.isNull()) { action->setIcon(loader->getStandardTagIcon(album)); } else { action->setIcon(pix); } } else { // for the time while loading, set standard icon // usually this code path will not be used, as icons are cached action->setIcon(loader->getStandardTagIcon(album)); } } void TagsPopupMenu::slotToggleTag(QAction* action) { int tagID = action->data().toInt(); emit signalTagActivated(tagID); } void TagsPopupMenu::slotAddTag(QAction* action) { int tagID = action->data().toInt(); AlbumManager* const man = AlbumManager::instance(); if (tagID == -1) { emit signalPopupTagsView(); return; } TAlbum* const parent = man->findTAlbum(tagID); if (!parent) { qCWarning(DIGIKAM_GENERAL_LOG) << "Failed to find album with id " << tagID; return; } QString title, icon; QKeySequence ks; if (!TagEditDlg::tagCreate(qApp->activeWindow(), parent, title, icon, ks)) { return; } QMap errMap; AlbumList tList = TagEditDlg::createTAlbum(parent, title, icon, ks, errMap); TagEditDlg::showtagsListCreationError(qApp->activeWindow(), errMap); for (AlbumList::const_iterator it = tList.constBegin() ; it != tList.constEnd() ; ++it) { emit signalTagActivated((*it)->id()); } } void TagsPopupMenu::slotTagThumbnail(Album* album, const QPixmap& pix) { QList actionList = actions(); - foreach(QAction* const action, actionList) + foreach (QAction* const action, actionList) { if (action->data().toInt() == album->id()) { action->setIcon(pix); return; } } } } // namespace Digikam diff --git a/core/libs/template/templatelist.cpp b/core/libs/template/templatelist.cpp index d313e5fdf3..b9626b3b02 100644 --- a/core/libs/template/templatelist.cpp +++ b/core/libs/template/templatelist.cpp @@ -1,167 +1,167 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-06-20 * Description : identity list view. * * Copyright (C) 2009-2019 by Gilles Caulier * * 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, 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. * * ============================================================ */ #include "templatelist.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "templatemanager.h" #include "template.h" namespace Digikam { TemplateListItem::TemplateListItem(QTreeWidget* const parent, const Template& t) : QTreeWidgetItem(parent) { setTemplate(t); } TemplateListItem::~TemplateListItem() { } void TemplateListItem::setTemplate(const Template& t) { m_template = t; if (!m_template.isNull()) { setText(0, m_template.templateTitle()); setText(1, m_template.authors().join(QLatin1Char(';'))); } } Template TemplateListItem::getTemplate() const { return m_template; } // ------------------------------------------------------------------- TemplateList::TemplateList(QWidget* const parent) : QTreeWidget(parent) { setColumnCount(2); setRootIsDecorated(false); setSelectionMode(QAbstractItemView::SingleSelection); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAllColumnsShowFocus(true); setWhatsThis(i18n("Here you can see the metadata template list managed by digiKam.")); QStringList labels; labels.append(i18n("Title")); labels.append(i18n("Authors")); setHeaderLabels(labels); header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); header()->setSectionResizeMode(1, QHeaderView::Stretch); } TemplateList::~TemplateList() { } void TemplateList::readSettings() { TemplateManager* const tm = TemplateManager::defaultManager(); if (tm) { QList