diff --git a/NEWS b/NEWS index 2be007cf82..f315ffc1f4 100644 --- a/NEWS +++ b/NEWS @@ -1,42 +1,50 @@ digiKam 6.4.0 - Release date: 2019-??-?? ***************************************************************************************************** NEW FEATURES: -General : new RawImport plugin interface to delegate Raw decoding function to extra engine ImageEditor. +General : new RawImport plugin interface to delegate Raw decoding function to extra engine with ImageEditor. ImageEditor: add new setting from setup dialog to select right Raw Import plugin. ImageEditor: add new clone tool to fix artifacts on image. +ImageEditor: add new tool to import RAW image using UFRaw. +ImageEditor: add new tool to import RAW image using RawTherapee. +ImageEditor: add new tool to import RAW image using DarkTable. ***************************************************************************************************** BUGFIXES: 001 ==> 411587 - [digiKam] Crash when reopening Google Photos import wizard. 002 ==> 411578 - White Balance changes global luminosity. 003 ==> 411696 - Cannot launch digikam-6.3.0-x86-64.appimage in Kubuntu 14.04. 004 ==> 406503 - Histogram initializes greyer than white. 005 ==> 411714 - Correction of ticket 408881 (Restore default tools settings) is not enforced. 006 ==> 411702 - Video/picture Filter. 007 ==> 411726 - Geolocation not working. 008 ==> 387768 - File system corruption after renaming folder. 009 ==> 411808 - digiKam quit unexpectedly. 010 ==> 411880 - Existing file is count but not displayed. 011 ==> 411882 - I don't get image stack or panorama. 012 ==> 403269 - Stacked image and Panorama tools cannot find required binaries. 013 ==> 411651 - New tool to export photos to Canon Irista. 014 ==> 411927 - Crash during initial scan. 015 ==> 389652 - Interface freezes during initial scan. 016 ==> 389949 - Very slow startup [patch]. 017 ==> 316865 - SCAN : Add new option to don't scan file bigger than n Mb or stop scan if longer than n seconds. 018 ==> 370019 - Sidecar metadata not loading sporadically. 019 ==> 329353 - Make slow processing better. 020 ==> 392090 - While scanning collection the progess bar shows 0%. 021 ==> 411929 - Can not move or delete videos that were not played till the end. 022 ==> 392727 - Images are missing (Windows). 023 ==> 396559 - "digikam.dbengine: Database is locked." when scanning for new items. 024 ==> 411946 - Crash when saving captions to picture. 025 ==> 411902 - Interface icons become very large and unusable. 026 ==> 330168 - MYSQL : allow read only database. 027 ==> 351658 - Prevent to fill whole memory when all CPU cores are used to process Maintenance tools. 028 ==> 110920 - Support for removing complex objects from photos. 029 ==> 103332 - Blurring brushes for image correction. -030 ==> +030 ==> 150161 - UFRaw as tool for RAW. +032 ==> 411214 - digiKam git beta 6.4 eats huge memory. +033 ==> 181941 - Add an option to image editor to remember or not tools settings between sessions. +034 ==> 221571 - Integrate RawTherapee into digikam +035 ==> 341186 - Integrate with darktable. +036 ==> diff --git a/core/dplugins/editor/enhance/healingclone/healingclonetool.cpp b/core/dplugins/editor/enhance/healingclone/healingclonetool.cpp index 2aa7f68e43..10e0481d8a 100644 --- a/core/dplugins/editor/enhance/healingclone/healingclonetool.cpp +++ b/core/dplugins/editor/enhance/healingclone/healingclonetool.cpp @@ -1,690 +1,693 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2017-06-15 * Description : a tool to replace part of the image using another * * Copyright (C) 2004-2019 by Gilles Caulier * Copyright (C) 2017 by Shaza Ismail Kaoud * Copyright (C) 2019 by Ahmed Fathi * * 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 "healingclonetool.h" // C++ includes #include // Qt includes #include #include -#include #include #include #include #include // KDE includes #include #include // Local includes #include "dexpanderbox.h" #include "dnuminput.h" #include "editortoolsettings.h" #include "imageiface.h" +#include "itempropertiestxtlabel.h" #include "healingclonetoolwidget.h" namespace DigikamEditorHealingCloneToolPlugin { class Q_DECL_HIDDEN HealingCloneTool::Private { public: explicit Private() : btnSize(QSize(50, 50)), iconSize(QSize(30, 30)), radiusInput(nullptr), blurPercent(nullptr), previewWidget(nullptr), gboxSettings(nullptr), srcButton(nullptr), lassoButton(nullptr), moveButton(nullptr), undoCloneButton(nullptr), redoCloneButton(nullptr), resetLassoPoint(true), insideLassoOperation(false) { } static const QString configGroupName; static const QString configRadiusAdjustmentEntry; static const QString configBlurAdjustmentEntry; const QSize btnSize; const QSize iconSize; DIntNumInput* radiusInput; DDoubleNumInput* blurPercent; HealingCloneToolWidget* previewWidget; EditorToolSettings* gboxSettings; QPushButton* srcButton; QPushButton* lassoButton; QPushButton* moveButton; QPushButton* undoCloneButton; QPushButton* redoCloneButton; std::stack undoStack; std::stack redoStack; bool resetLassoPoint; bool insideLassoOperation; QPoint previousLassoPoint; QPoint startLassoPoint; std::vector lassoColors; std::vector lassoPoints; QPolygon lassoPolygon; std::vector> lassoFlags; std::map, DColor> lassoColorsMap; }; const QString HealingCloneTool::Private::configGroupName(QLatin1String("Healing Clone Tool")); const QString HealingCloneTool::Private::configRadiusAdjustmentEntry(QLatin1String("RadiusAdjustment")); const QString HealingCloneTool::Private::configBlurAdjustmentEntry(QLatin1String("BlurAdjustment")); // -------------------------------------------------------- HealingCloneTool::HealingCloneTool(QObject* const parent) : EditorTool(parent), d(new Private) { setObjectName(QLatin1String("healing clone")); setToolHelp(QLatin1String("healingclonetool.anchor")); d->gboxSettings = new EditorToolSettings(0); d->previewWidget = new HealingCloneToolWidget; refreshImage(); d->previewWidget->setFocusPolicy(Qt::StrongFocus); setToolView(d->previewWidget); setPreviewModeMask(PreviewToolBar::PreviewTargetImage); // -------------------------------------------------------- QLabel* const label = new QLabel(i18n("Brush Radius:")); d->radiusInput = new DIntNumInput(); d->radiusInput->setRange(0, 100, 1); d->radiusInput->setDefaultValue(10); d->radiusInput->setWhatsThis(i18n("A radius of 0 has no effect, " "1 and above determine the brush radius " "that determines the size of parts copied in the image. \nShortcut :: [ and ]")); d->radiusInput->setToolTip(i18n("A radius of 0 has no effect, " "1 and above determine the brush radius " "that determines the size of parts copied in the image. \nShortcut :: [ and ]")); d->previewWidget->setBrushRadius(d->radiusInput->value()); // -------------------------------------------------------- QLabel* const label2 = new QLabel(i18n("Radial Blur Percent:")); d->blurPercent = new DDoubleNumInput(); d->blurPercent->setRange(0, 100, 0.1); d->blurPercent->setDefaultValue(0); d->blurPercent->setWhatsThis(i18n("A percent of 0 has no effect, values " "above 0 represent a factor for mixing " "the destination color with source color " "this is done radially i.e. the inner part of " "the brush radius is totally from source and mixing " "with destination is done gradually till the outer part " "of the circle.")); // -------------------------------------------------------- QPixmap sourcePixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/healing_clone_SRC.png"))); const QIcon sourceBtnIcon(sourcePixmap); d->srcButton = new QPushButton(); d->srcButton->setFixedSize(d->btnSize); d->srcButton->setIcon(sourceBtnIcon); d->srcButton->setIconSize(d->iconSize); d->srcButton->setWhatsThis(i18n("Select Source Point.\nShortcut: S")); d->srcButton->setToolTip(i18n("Select Source Point.\nShortcut: S")); // -------------------------------------------------------- QPixmap lassoPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/healing_clone_LASSO.png"))); const QIcon lassoBtnIcon(lassoPixmap); d->lassoButton = new QPushButton(); d->lassoButton->setFixedSize(d->btnSize); d->lassoButton->setIcon(lassoBtnIcon); d->lassoButton->setIconSize(d->iconSize); d->lassoButton->setWhatsThis(i18n("Polygon Selection With Lasso.\nShortcut: L\n" "To Continue polygon, either press L or double click\n" "To Cancel, press ESC")); d->lassoButton->setToolTip(i18n("Polygon Selection With Lasso.\nShortcut: L\n" "To Continue polygon, either press L or double click\n" "To Cancel, press ESC")); // -------------------------------------------------------- QPixmap movePixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/healing_clone_MOVE.png"))); const QIcon moveBtnIcon(movePixmap); d->moveButton = new QPushButton(); d->moveButton->setFixedSize(d->btnSize); d->moveButton->setIcon(moveBtnIcon); d->moveButton->setIconSize(d->iconSize); d->moveButton->setWhatsThis(i18n("Move Image.\nShortcut: M")); d->moveButton->setToolTip(i18n("Move Image.\nShortcut: M")); // -------------------------------------------------------- QPixmap undoPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/healing_clone_UNDO.png"))); const QIcon undoBtnIcon(undoPixmap); d->undoCloneButton = new QPushButton(); d->undoCloneButton->setFixedSize(d->btnSize); d->undoCloneButton->setIcon(undoBtnIcon); d->undoCloneButton->setIconSize(d->iconSize); d->undoCloneButton->setWhatsThis(i18n("Undo clone operation.\nShortcut: CTRL+Z")); d->undoCloneButton->setToolTip(i18n("Undo clone operation.\nShortcut: CTRL+Z")); // -------------------------------------------------------- QPixmap redoPixmap(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/healing_clone_REDO.png"))); const QIcon redoBtnIcon(redoPixmap); d->redoCloneButton = new QPushButton(); d->redoCloneButton->setFixedSize(d->btnSize); d->redoCloneButton->setIcon(redoBtnIcon); d->redoCloneButton->setIconSize(d->iconSize); d->redoCloneButton->setWhatsThis(i18n("Redo clone operation.\nShortcut: CTRL+Y")); d->redoCloneButton->setToolTip(i18n("Redo clone operation.\nShortcut: CTRL+Y")); + // -------------------------------------------------------- - QPlainTextEdit* const label3 = new QPlainTextEdit(i18n("How To Use:\n====\n" - "* Press s to switch to source-selection mode, and select source point on image.\n" - "* Press s again and start cloning.\n" - "* Press [ and ] to change brush size.\n" - "* Press CTRL+Mousewheel to zoom in/out.\n" - "* Press m to pan the image if image is larger than viewport.\n" - "* Press l to start lasso mode. Start drawing lasso boundary either " - "continuously or discretely, then double-click or press l again to close the boundary.\n" - "* Inside lasso mode, you can clone only inside the lasso region.\n")); - label3->setReadOnly(true); + QString help = i18n("

How To Use:

" + "* Press s to switch to source-selection mode, and select source point on image.
" + "* Press s again and start cloning.
" + "* Press [ and ] to change brush size.
" + "* Press CTRL+Mousewheel to zoom in/out.
" + "* Press m to pan the image if image is larger than viewport.
" + "* Press l to start lasso mode. Start drawing lasso boundary either " + "continuously or discretely, then double-click or press l again to close the boundary.
" + "* Inside lasso mode, you can clone only inside the lasso region.

"); + + DTextBrowser* const label3 = new DTextBrowser(help); + label3->setLinesNumber(20); // Tool Buttons const int spacing = d->gboxSettings->spacingHint(); QGridLayout* const grid = new QGridLayout(); QGroupBox* const iconsGroupBox = new QGroupBox(); QHBoxLayout* const iconsHBox = new QHBoxLayout(); iconsHBox->setSpacing(0); iconsHBox->addWidget(d->srcButton); iconsHBox->addWidget(d->lassoButton); iconsHBox->addWidget(d->moveButton); iconsHBox->addWidget(d->undoCloneButton); iconsHBox->addWidget(d->redoCloneButton); iconsGroupBox->setLayout(iconsHBox); // --- grid->addWidget(iconsGroupBox); grid->addWidget(new DLineWidget(Qt::Horizontal, d->gboxSettings->plainPage()), 3, 0, 1, 2); grid->addWidget(label, 4, 0, 1, 2); grid->addWidget(d->radiusInput, 5, 0, 1, 2); grid->addWidget(label2, 6, 0, 1, 2); grid->addWidget(d->blurPercent, 7, 0, 1, 2); grid->addWidget(new DLineWidget(Qt::Horizontal, d->gboxSettings->plainPage()), 8, 0, 1, 2); grid->addWidget(label3, 9, 0, 1, 2); grid->setRowStretch(10, 10); grid->setContentsMargins(spacing, spacing, spacing, spacing); grid->setSpacing(spacing); d->gboxSettings->plainPage()->setLayout(grid); // -------------------------------------------------------- setPreviewModeMask(PreviewToolBar::PreviewTargetImage); setToolSettings(d->gboxSettings); setToolView(d->previewWidget); // -------------------------------------------------------- d->lassoColors.push_back(DColor(Qt::red)); d->lassoColors.push_back(DColor(Qt::white)); d->lassoColors.push_back(DColor(Qt::black)); d->lassoColors.push_back(DColor(Qt::yellow)); d->lassoColors.push_back(DColor(Qt::blue)); d->lassoColors.push_back(DColor(Qt::yellow)); // -------------------------------------------------------- connect(d->radiusInput, SIGNAL(valueChanged(int)), this, SLOT(slotRadiusChanged(int))); connect(d->srcButton, SIGNAL(clicked(bool)), d->previewWidget, SLOT(slotSetSourcePoint())); connect(d->moveButton, SIGNAL(clicked(bool)), d->previewWidget, SLOT(slotMoveImage())); connect(d->lassoButton, SIGNAL(clicked(bool)), d->previewWidget, SLOT(slotLassoSelect())); connect(d->undoCloneButton, SIGNAL(clicked(bool)), this, SLOT(slotUndoClone())); connect(d->redoCloneButton, SIGNAL(clicked(bool)), this, SLOT(slotRedoClone())); connect(d->previewWidget, SIGNAL(signalClone(QPoint,QPoint)), this, SLOT(slotReplace(QPoint,QPoint))); connect(d->previewWidget, SIGNAL(signalLasso(QPoint)), this, SLOT(slotLasso(QPoint))); connect(d->previewWidget, SIGNAL(signalResetLassoPoint()), this, SLOT(slotResetLassoPoints())); connect(d->previewWidget,SIGNAL(signalContinuePolygon()), this, SLOT(slotContinuePolygon())); connect(d->previewWidget,SIGNAL(signalIncreaseBrushRadius()), this, SLOT(slotIncreaseBrushRadius())); connect(d->previewWidget,SIGNAL(signalDecreaseBrushRadius()), this, SLOT(slotDecreaseBrushRadius())); // undo - redo connect(d->previewWidget,SIGNAL(signalPushToUndoStack()), this, SLOT(slotPushToUndoStack())); connect(d->previewWidget,SIGNAL(signalUndoClone()), this, SLOT(slotUndoClone())); connect(d->previewWidget,SIGNAL(signalRedoClone()), this, SLOT(slotRedoClone())); } HealingCloneTool::~HealingCloneTool() { delete d; } void HealingCloneTool::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); d->radiusInput->setValue(group.readEntry(d->configRadiusAdjustmentEntry, d->radiusInput->defaultValue())); d->blurPercent->setValue(group.readEntry(d->configBlurAdjustmentEntry, d->blurPercent->defaultValue())); } void HealingCloneTool::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(d->configGroupName); group.writeEntry(d->configRadiusAdjustmentEntry, d->radiusInput->value()); group.writeEntry(d->configBlurAdjustmentEntry, d->blurPercent->value()); config->sync(); } void HealingCloneTool::finalRendering() { ImageIface iface; DImg dest = d->previewWidget->getOriginalImage(); FilterAction action(QLatin1String("digikam:healingCloneTool"), 1); iface.setOriginal(i18n("healingClone"), action, dest); } void HealingCloneTool::slotResetSettings() { d->radiusInput->blockSignals(true); d->radiusInput->slotReset(); d->radiusInput->blockSignals(false); } void HealingCloneTool::slotResized() { toolView()->update(); } void HealingCloneTool::slotReplace(const QPoint& srcPoint, const QPoint& dstPoint) { DImg current = d->previewWidget->getOriginalImage(); clone(¤t, srcPoint, dstPoint, d->radiusInput->value()); } void HealingCloneTool::slotRadiusChanged(int r) { d->previewWidget->setBrushRadius(r); } void HealingCloneTool::clone(DImg* const img, const QPoint& srcPoint, const QPoint& dstPoint, int radius) { ImageRegionItem* const item = dynamic_cast(d->previewWidget->item()); if (!item) { return; } double scale = item->zoomSettings()->zoomFactor(); radius = radius / scale; double blurPercent = d->blurPercent->value() / 100; for (int i = -1 * radius ; i < radius ; ++i) { for (int j = -1 * radius ; j < radius ; ++j) { int rPercent = (i * i) + (j * j); if (rPercent < (radius * radius)) // Check for inside the circle { if ((srcPoint.x() + i < 0) || (srcPoint.x() + i >= (int)img->width()) || (srcPoint.y() + j < 0) || (srcPoint.y() + j >= (int)img->height()) || (dstPoint.x() + i < 0) || (dstPoint.x() + i >= (int)img->width()) || (dstPoint.y() + j < 0) || (dstPoint.y() + j >= (int)img->height())) { continue; } DColor cSrc = img->getPixelColor(srcPoint.x() + i, srcPoint.y() + j); if (d->insideLassoOperation && !d->lassoPoints.empty()) { if (d->lassoFlags.at(dstPoint.x() + i).at(dstPoint.y() + j)) { continue; } bool isInside = d->lassoPolygon.containsPoint(QPoint(dstPoint.x() + i, dstPoint.y() + j), Qt::OddEvenFill); if (!isInside) { continue; } if (d->lassoFlags.at(srcPoint.x() + i).at(srcPoint.y() + j)) { cSrc = d->lassoColorsMap[std::make_pair(srcPoint.x() + i, srcPoint.y() + j)]; } } double rP = blurPercent * rPercent / (radius * radius); DColor cDst = img->getPixelColor(dstPoint.x() + i, dstPoint.y() + j); cSrc.multiply(1 - rP); cDst.multiply(rP); cSrc.blendAdd(cDst); img->setPixelColor(dstPoint.x() + i, dstPoint.y() + j, cSrc); d->previewWidget->setCloneVectorChanged(true); } } } d->previewWidget->updateImage(*img); } void HealingCloneTool::updateLasso(std::vector& points) { uint radius = 5; static uint colorCounter = 0; DImg img = d->previewWidget->getOriginalImage(); foreach (const QPoint& p, points) { for (uint i = 0 ; i < radius ; ++i) { for (uint j = 0 ; j < radius ; ++j) { uint x_shifted = p.x() + i; uint y_shifted = p.y() + j; DColor c = img.getPixelColor(x_shifted, y_shifted); d->lassoColorsMap.insert(std::make_pair(std::make_pair(x_shifted, y_shifted), c)); img.setPixelColor(x_shifted, y_shifted, d->lassoColors[(colorCounter) % d->lassoColors.size()]); d->lassoFlags.at(x_shifted).at(y_shifted) = true; colorCounter++; } } } d->previewWidget->updateImage(img); } void HealingCloneTool::slotLasso(const QPoint& dst) { if (d->resetLassoPoint) { d->previousLassoPoint = dst; d->resetLassoPoint = false; d->startLassoPoint = dst; } std::vector points = interpolate(d->previousLassoPoint, dst); d->lassoPoints.push_back(dst); d->previousLassoPoint = dst; updateLasso(points); d->previewWidget->setIsLassoPointsVectorEmpty(d->lassoPoints.empty()); } std::vector HealingCloneTool::interpolate(const QPoint& start, const QPoint& end) { std::vector points; points.push_back(start); QPointF distanceVec = QPoint(end.x()-start.x(), end.y() - start.y()); double distance = sqrt(distanceVec.x() * distanceVec.x() + distanceVec.y() * distanceVec.y()); //creating a unit vector distanceVec.setX(distanceVec.x() / distance); distanceVec.setY(distanceVec.y() / distance); int steps = (int) distance; for (int i = 0 ; i < steps ; ++i) { points.push_back(QPoint(start.x() + i * distanceVec.x(), start.y() + i * distanceVec.y())); } points.push_back(end); return points; } void HealingCloneTool::removeLassoPixels() { DImg img = d->previewWidget->getOriginalImage(); std::map, DColor>::iterator it; for (it = d->lassoColorsMap.begin() ; it != d->lassoColorsMap.end() ; it++) { std::pair xy = it->first; DColor color = it->second; img.setPixelColor(xy.first, xy.second, color); } d->previewWidget->updateImage(img); } void HealingCloneTool::redrawLassoPixels() { int colorCounter = 0; DImg img = d->previewWidget->getOriginalImage(); std::map, DColor>::iterator it; for (it = d->lassoColorsMap.begin() ; it != d->lassoColorsMap.end() ; it++) { colorCounter++; DColor color = d->lassoColors[(colorCounter) % d->lassoColors.size()]; std::pair xy = it->first; img.setPixelColor(xy.first, xy.second, color); } d->previewWidget->updateImage(img); } void HealingCloneTool::slotResetLassoPoints() { removeLassoPixels(); d->resetLassoPoint = true; d->lassoPoints.clear(); d->insideLassoOperation = true; d->lassoPolygon.clear(); d->lassoColorsMap.clear(); initializeLassoFlags(); d->previewWidget->setIsLassoPointsVectorEmpty(d->lassoPoints.empty()); } void HealingCloneTool::slotContinuePolygon() { if (d->lassoPoints.empty()) { return; } QPoint& start = d->startLassoPoint; QPoint& end = d->previousLassoPoint; std::vector points = interpolate(end,start); updateLasso(points); d->lassoPoints.push_back(start); QVector polygon; foreach (const QPoint& point, d->lassoPoints) { polygon.append(point); } d->lassoPolygon = QPolygon(polygon); } void HealingCloneTool::slotIncreaseBrushRadius() { int size = d->radiusInput->value(); d->radiusInput->setValue(size + 1); } void HealingCloneTool::slotDecreaseBrushRadius() { int size = d->radiusInput->value(); d->radiusInput->setValue(size - 1); } void HealingCloneTool::initializeLassoFlags() { DImg img = d->previewWidget->getOriginalImage(); int w = img.width(); int h = img.height(); d->lassoFlags.resize(w); for (int i = 0 ; i < w ; ++i) { d->lassoFlags.at(i).resize(h); } for (int i = 0 ; i < w ; ++i) { for (int j = 0 ; j < h ; ++j) { d->lassoFlags.at(i).at(j) = false; } } } void HealingCloneTool::slotPushToUndoStack() { d->redoStack = std::stack(); removeLassoPixels(); d->undoStack.push(d->previewWidget->getOriginalImage()); redrawLassoPixels(); } void HealingCloneTool::slotUndoClone() { if (d->undoStack.empty()) { return; } removeLassoPixels(); d->redoStack.push(d->previewWidget->getOriginalImage()); DImg temp = d->undoStack.top(); d->undoStack.pop(); d->previewWidget->updateImage(temp); redrawLassoPixels(); } void HealingCloneTool::slotRedoClone() { // slotResetLassoPoints(); if (d->redoStack.empty()) { return; } removeLassoPixels(); d->undoStack.push(d->previewWidget->getOriginalImage()); DImg temp = d->redoStack.top(); d->redoStack.pop(); d->previewWidget->updateImage(temp); redrawLassoPixels(); } void HealingCloneTool::refreshImage() { ImageRegionWidget* const wgt = dynamic_cast(d->previewWidget); if (wgt) { QRectF test = wgt->sceneRect(); ImageRegionItem* const item = dynamic_cast(wgt->item()); if (item) { int w = item->boundingRect().width(); int h = item->boundingRect().height(); test.setWidth(10); test.setHeight(10); wgt->fitInView(test, Qt::KeepAspectRatio); test.setWidth(w); test.setHeight(h); wgt->fitInView(test, Qt::KeepAspectRatio); } } } } // namespace DigikamEditorHealingCloneToolPlugin diff --git a/core/dplugins/rawimport/CMakeLists.txt b/core/dplugins/rawimport/CMakeLists.txt index 8c10e91c46..1492f7ef21 100644 --- a/core/dplugins/rawimport/CMakeLists.txt +++ b/core/dplugins/rawimport/CMakeLists.txt @@ -1,7 +1,11 @@ # # Copyright (c) 2010-2019 by Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# NOTE: Native plugin use internal libraw from digiKam core. add_subdirectory(native) +add_subdirectory(ufraw) +add_subdirectory(rawtherapee) +add_subdirectory(darktable) diff --git a/core/dplugins/rawimport/darktable/CMakeLists.txt b/core/dplugins/rawimport/darktable/CMakeLists.txt new file mode 100644 index 0000000000..4cf92e6b96 --- /dev/null +++ b/core/dplugins/rawimport/darktable/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2015-2019 by Gilles Caulier, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(MacroDPlugins) + +include_directories($ + $ + $ + + $ + $ +) + +set(rawimportdarktableplugin_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/rawimportdarktableplugin.cpp +) + +DIGIKAM_ADD_RAWIMPORT_PLUGIN(NAME DarkTable + SOURCES ${rawimportdarktableplugin_SRCS} +) diff --git a/core/dplugins/rawimport/darktable/rawimportdarktableplugin.cpp b/core/dplugins/rawimport/darktable/rawimportdarktableplugin.cpp new file mode 100644 index 0000000000..536bc60f20 --- /dev/null +++ b/core/dplugins/rawimport/darktable/rawimportdarktableplugin.cpp @@ -0,0 +1,249 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : DarkTable raw import plugin. + * + * Copyright (C) 2019 by Gilles Caulier + * + * Lua script inspired from Darktable Gimp plugin + * Copyright (C) 2015-2017 by Tobias Ellinghaus + * + * 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 "rawimportdarktableplugin.h" + +// Qt includes + +#include +#include +#include +#include +#include +#include + +// KDE includes + +#include + +// Local includes + +#include "digikam_debug.h" + +namespace DigikamRawImportDarkTablePlugin +{ + +const QString s_luaScriptData = QLatin1String("\n" \ +"local dt = require \"darktable\"\n" \ +"\n" \ +"local min_api_version = \"2.1.0\"\n" \ +"if dt.configuration.api_version_string < min_api_version then\n" \ +" dt.print(\"the exit export script requires at least darktable version 1.7.0\")\n" \ +" dt.print_error(\"the exit export script requires at least darktable version 1.7.0\")\n" \ +" return\n" \ +"else\n" \ +" dt.print(\"closing darktable will export the image and make image editor load it\")\n" \ +"end\n" \ +"\n" \ +"local export_filename = dt.preferences.read(\"export_on_exit\", \"export_filename\", \"string\")\n" \ +"\n" \ +"dt.register_event(\"exit\", function()\n" \ +" -- safegurad against someone using this with their library containing 50k images\n" \ +" if #dt.database > 1 then\n" \ +" dt.print_error(\"too many images, only exporting the first\")\n" \ +" -- return\n" \ +" end\n" \ +"\n" \ +" -- change the view first to force writing of the history stack\n" \ +" dt.gui.current_view(dt.gui.views.lighttable)\n" \ +" -- now export\n" \ +" local format = dt.new_format(\"png\")\n" \ +" format.max_width = 0\n" \ +" format.max_height = 0\n" \ +" -- lets have the export in a loop so we could easily support > 1 images\n" \ +" for _, image in ipairs(dt.database) do\n" \ +" dt.print_error(\"exporting `\"..tostring(image)..\"' to `\"..export_filename..\"'\")\n" \ +" format:write_image(image, export_filename)\n" \ +" break -- only export one image. see above for the reason\n" \ +" end\n" \ +"end)\n" \ +""); + +DarkTableRawImportPlugin::DarkTableRawImportPlugin(QObject* const parent) + : DPluginRawImport(parent), + m_darktable(nullptr), + m_tempFile(nullptr) +{ + m_luaFile.open(); + QTextStream stream(&m_luaFile); + stream << s_luaScriptData; + stream.flush(); +} + +DarkTableRawImportPlugin::~DarkTableRawImportPlugin() +{ +} + +QString DarkTableRawImportPlugin::name() const +{ + return QString::fromUtf8("Raw Import using DarkTable"); +} + +QString DarkTableRawImportPlugin::iid() const +{ + return QLatin1String(DPLUGIN_IID); +} + +QIcon DarkTableRawImportPlugin::icon() const +{ + return QIcon::fromTheme(QLatin1String("image-x-adobe-dng")); +} + +QString DarkTableRawImportPlugin::description() const +{ + return QString::fromUtf8("A RAW import plugin based on DarkTable"); +} + +QString DarkTableRawImportPlugin::details() const +{ + return QString::fromUtf8("

This RAW Import plugin use DarkTable tool to pre-process file in Image Editor.

"); +} + +QList DarkTableRawImportPlugin::authors() const +{ + return QList() + << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), + QString::fromUtf8("caulier dot gilles at gmail dot com"), + QString::fromUtf8("(C) 2019")) + ; +} + +void DarkTableRawImportPlugin::setup(QObject* const /*parent*/) +{ + // Nothing to do +} + +bool DarkTableRawImportPlugin::run(const QString& filePath, const DRawDecoding& /*def*/) +{ + m_fileInfo = QFileInfo(filePath); + m_props = LoadingDescription(m_fileInfo.filePath(), LoadingDescription::ConvertForEditor); + m_decoded = DImg(); + + delete m_tempFile; + + m_tempFile = new QTemporaryFile(); + m_tempFile->open(); + + m_darktable = new QProcess(this); + m_darktable->setProcessChannelMode(QProcess::MergedChannels); + m_darktable->setWorkingDirectory(m_fileInfo.path()); + + connect(m_darktable, SIGNAL(errorOccurred(QProcess::ProcessError)), + this, SLOT(slotErrorOccurred(QProcess::ProcessError))); + + connect(m_darktable, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); + + connect(m_darktable, SIGNAL(readyRead()), + this, SLOT(slotProcessReadyRead())); + + // -------- + + m_fileInfo = QFileInfo(filePath); + + m_darktable->setProgram(QLatin1String("darktable")); + m_darktable->setArguments(QStringList() << QLatin1String("--library") + << QLatin1String(":memory:") // Run DarkTable to process only one file + << QLatin1String("--luacmd") + << QString::fromUtf8("dofile('%1')") + .arg(m_luaFile.fileName()) // LUA script to run in DarkTable + << QLatin1String("--conf") + << QLatin1String("plugins/lighttable/export/icctype=3") // Output color-space + << QLatin1String("--conf") + << QString::fromUtf8("lua/export_on_exit/export_filename=%1") + .arg(m_tempFile->fileName()) // Ouput file + << filePath); // Input file + + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable arguments:" << m_darktable->arguments(); + + m_darktable->start(); + + return true; +} + +void DarkTableRawImportPlugin::slotErrorOccurred(QProcess::ProcessError error) +{ + switch (error) + { + case QProcess::FailedToStart: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process has failed to start"; + break; + case QProcess::Crashed: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process has crashed"; + break; + case QProcess::Timedout: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process time-out"; + break; + case QProcess::WriteError: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process write error"; + break; + case QProcess::ReadError: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process read error"; + break; + default: + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process error unknown"; + break; + } +} + +void DarkTableRawImportPlugin::slotProcessFinished(int code, QProcess::ExitStatus status) +{ + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: return code:" << code << ":: Exit status:" << status; + + m_decoded = DImg(m_tempFile->fileName()); + + if (m_decoded.isNull()) + { + QString message = i18n("Error to import RAW image with DarkTable\nClose this dialog to load RAW image with native import tool"); + QMessageBox::information(0, qApp->applicationName(), message); + + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is null! Load with Native tool..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + emit signalLoadRaw(m_props); + } + else + { + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is not null..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + m_props = LoadingDescription(m_tempFile->fileName(), LoadingDescription::ConvertForEditor); + emit signalDecodedImage(m_props, m_decoded); + } + + delete m_tempFile; + m_tempFile = nullptr; +} + +void DarkTableRawImportPlugin::slotProcessReadyRead() +{ + QByteArray data = m_darktable->readAllStandardError(); + QStringList lines = QString::fromUtf8(data).split(QLatin1Char('\n'), QString::SkipEmptyParts); + + foreach (const QString& one, lines) + { + qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable ::" << one; + } +} + +} // namespace DigikamRawImportDarkTablePlugin diff --git a/core/dplugins/rawimport/darktable/rawimportdarktableplugin.h b/core/dplugins/rawimport/darktable/rawimportdarktableplugin.h new file mode 100644 index 0000000000..3b784be852 --- /dev/null +++ b/core/dplugins/rawimport/darktable/rawimportdarktableplugin.h @@ -0,0 +1,85 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : DarkTable raw import plugin. + * + * Copyright (C) 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_DARKTABLE_RAW_IMPORT_PLUGIN_H +#define DIGIKAM_DARKTABLE_RAW_IMPORT_PLUGIN_H + +// Qt includes + +#include +#include +#include + +// Local includes + +#include "dpluginrawimport.h" +#include "dimg.h" +#include "loadingdescription.h" + +#define DPLUGIN_IID "org.kde.digikam.plugin.rawimport.DarkTable" + +using namespace Digikam; + +namespace DigikamRawImportDarkTablePlugin +{ + +class DarkTableRawImportPlugin : public DPluginRawImport +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID DPLUGIN_IID) + Q_INTERFACES(Digikam::DPluginRawImport) + +public: + + explicit DarkTableRawImportPlugin(QObject* const parent = nullptr); + ~DarkTableRawImportPlugin(); + + QString name() const override; + QString iid() const override; + QIcon icon() const override; + QString details() const override; + QString description() const override; + QList authors() const override; + + void setup(QObject* const) override; + + bool run(const QString& filePath, const DRawDecoding& def); + +private Q_SLOTS: + + void slotErrorOccurred(QProcess::ProcessError); + void slotProcessFinished(int, QProcess::ExitStatus); + void slotProcessReadyRead(); + +private: + + QProcess* m_darktable; + DImg m_decoded; + LoadingDescription m_props; + QFileInfo m_fileInfo; + QTemporaryFile* m_tempFile; + QTemporaryFile m_luaFile; +}; + +} // namespace DigikamRawImportDarkTablePlugin + +#endif // DIGIKAM_DARKTABLE_RAW_IMPORT_PLUGIN_H diff --git a/core/dplugins/rawimport/rawtherapee/CMakeLists.txt b/core/dplugins/rawimport/rawtherapee/CMakeLists.txt new file mode 100644 index 0000000000..c6fc256fdc --- /dev/null +++ b/core/dplugins/rawimport/rawtherapee/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2015-2019 by Gilles Caulier, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(MacroDPlugins) + +include_directories($ + $ + $ + + $ + $ +) + +set(rawimportrawtherapeeplugin_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/rawimportrawtherapeeplugin.cpp +) + +DIGIKAM_ADD_RAWIMPORT_PLUGIN(NAME RawTherapee + SOURCES ${rawimportrawtherapeeplugin_SRCS} +) diff --git a/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.cpp b/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.cpp new file mode 100644 index 0000000000..1343136b5b --- /dev/null +++ b/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.cpp @@ -0,0 +1,197 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : RawTherapee raw import plugin. + * + * Copyright (C) 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 "rawimportrawtherapeeplugin.h" + +// Qt includes + +#include +#include +#include +#include +#include + +// KDE includes + +#include + +// Local includes + +#include "digikam_debug.h" + +namespace DigikamRawImportRawTherapeePlugin +{ + +RawTherapeeRawImportPlugin::RawTherapeeRawImportPlugin(QObject* const parent) + : DPluginRawImport(parent), + m_rawtherapee(nullptr), + m_tempFile(nullptr) +{ +} + +RawTherapeeRawImportPlugin::~RawTherapeeRawImportPlugin() +{ +} + +QString RawTherapeeRawImportPlugin::name() const +{ + return QString::fromUtf8("Raw Import using RawTherapee"); +} + +QString RawTherapeeRawImportPlugin::iid() const +{ + return QLatin1String(DPLUGIN_IID); +} + +QIcon RawTherapeeRawImportPlugin::icon() const +{ + return QIcon::fromTheme(QLatin1String("image-x-adobe-dng")); +} + +QString RawTherapeeRawImportPlugin::description() const +{ + return QString::fromUtf8("A RAW import plugin based on RawTherapee"); +} + +QString RawTherapeeRawImportPlugin::details() const +{ + return QString::fromUtf8("

This RAW Import plugin use RawTherapee tool to pre-process file in Image Editor.

"); +} + +QList RawTherapeeRawImportPlugin::authors() const +{ + return QList() + << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), + QString::fromUtf8("caulier dot gilles at gmail dot com"), + QString::fromUtf8("(C) 2019")) + ; +} + +void RawTherapeeRawImportPlugin::setup(QObject* const /*parent*/) +{ + // Nothing to do +} + +bool RawTherapeeRawImportPlugin::run(const QString& filePath, const DRawDecoding& /*def*/) +{ + m_fileInfo = QFileInfo(filePath); + m_props = LoadingDescription(m_fileInfo.filePath(), LoadingDescription::ConvertForEditor); + m_decoded = DImg(); + + delete m_tempFile; + + m_tempFile = new QTemporaryFile(); + m_tempFile->open(); + + m_rawtherapee = new QProcess(this); + m_rawtherapee->setProcessChannelMode(QProcess::MergedChannels); + m_rawtherapee->setWorkingDirectory(m_fileInfo.path()); + + connect(m_rawtherapee, SIGNAL(errorOccurred(QProcess::ProcessError)), + this, SLOT(slotErrorOccurred(QProcess::ProcessError))); + + connect(m_rawtherapee, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); + + connect(m_rawtherapee, SIGNAL(readyRead()), + this, SLOT(slotProcessReadyRead())); + + // -------- + + m_fileInfo = QFileInfo(filePath); + + m_rawtherapee->setProgram(QLatin1String("rawtherapee")); + m_rawtherapee->setArguments(QStringList() << QLatin1String("-gimp") // Special mode used initialy as Gimp plugin + << filePath // Input file + << m_tempFile->fileName()); // Output file + + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee arguments:" << m_rawtherapee->arguments(); + + m_rawtherapee->start(); + + return true; +} + +void RawTherapeeRawImportPlugin::slotErrorOccurred(QProcess::ProcessError error) +{ + switch (error) + { + case QProcess::FailedToStart: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process has failed to start"; + break; + case QProcess::Crashed: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process has crashed"; + break; + case QProcess::Timedout: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process time-out"; + break; + case QProcess::WriteError: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process write error"; + break; + case QProcess::ReadError: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process read error"; + break; + default: + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: Process error unknown"; + break; + } +} + +void RawTherapeeRawImportPlugin::slotProcessFinished(int code, QProcess::ExitStatus status) +{ + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee :: return code:" << code << ":: Exit status:" << status; + + m_decoded = DImg(m_tempFile->fileName()); + + if (m_decoded.isNull()) + { + QString message = i18n("Error to import RAW image with RawTherapee\nClose this dialog to load RAW image with native import tool"); + QMessageBox::information(0, qApp->applicationName(), message); + + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is null! Load with Native tool..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + emit signalLoadRaw(m_props); + } + else + { + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is not null..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + m_props = LoadingDescription(m_tempFile->fileName(), LoadingDescription::ConvertForEditor); + emit signalDecodedImage(m_props, m_decoded); + } + + delete m_tempFile; + m_tempFile = nullptr; +} + +void RawTherapeeRawImportPlugin::slotProcessReadyRead() +{ + QByteArray data = m_rawtherapee->readAllStandardError(); + QStringList lines = QString::fromUtf8(data).split(QLatin1Char('\n'), QString::SkipEmptyParts); + + foreach (const QString& one, lines) + { + qCDebug(DIGIKAM_GENERAL_LOG) << "RawTherapee ::" << one; + } +} + +} // namespace DigikamRawImportRawTherapeePlugin diff --git a/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.h b/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.h new file mode 100644 index 0000000000..8257fde98b --- /dev/null +++ b/core/dplugins/rawimport/rawtherapee/rawimportrawtherapeeplugin.h @@ -0,0 +1,84 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : RawTherapee raw import plugin. + * + * Copyright (C) 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_RAWTHERAPEE_RAW_IMPORT_PLUGIN_H +#define DIGIKAM_RAWTHERAPEE_RAW_IMPORT_PLUGIN_H + +// Qt includes + +#include +#include +#include + +// Local includes + +#include "dpluginrawimport.h" +#include "dimg.h" +#include "loadingdescription.h" + +#define DPLUGIN_IID "org.kde.digikam.plugin.rawimport.RawTherapee" + +using namespace Digikam; + +namespace DigikamRawImportRawTherapeePlugin +{ + +class RawTherapeeRawImportPlugin : public DPluginRawImport +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID DPLUGIN_IID) + Q_INTERFACES(Digikam::DPluginRawImport) + +public: + + explicit RawTherapeeRawImportPlugin(QObject* const parent = nullptr); + ~RawTherapeeRawImportPlugin(); + + QString name() const override; + QString iid() const override; + QIcon icon() const override; + QString details() const override; + QString description() const override; + QList authors() const override; + + void setup(QObject* const) override; + + bool run(const QString& filePath, const DRawDecoding& def); + +private Q_SLOTS: + + void slotErrorOccurred(QProcess::ProcessError); + void slotProcessFinished(int, QProcess::ExitStatus); + void slotProcessReadyRead(); + +private: + + QProcess* m_rawtherapee; + DImg m_decoded; + LoadingDescription m_props; + QFileInfo m_fileInfo; + QTemporaryFile* m_tempFile; +}; + +} // namespace DigikamRawImportRawTherapeePlugin + +#endif // DIGIKAM_RAWTHERAPEE_RAW_IMPORT_PLUGIN_H diff --git a/core/dplugins/rawimport/ufraw/CMakeLists.txt b/core/dplugins/rawimport/ufraw/CMakeLists.txt new file mode 100644 index 0000000000..be24c60814 --- /dev/null +++ b/core/dplugins/rawimport/ufraw/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2015-2019 by Gilles Caulier, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(MacroDPlugins) + +include_directories($ + $ + $ + + $ + $ +) + +set(rawimportufrawplugin_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/rawimportufrawplugin.cpp +) + +DIGIKAM_ADD_RAWIMPORT_PLUGIN(NAME UFRaw + SOURCES ${rawimportufrawplugin_SRCS} +) diff --git a/core/dplugins/rawimport/ufraw/rawimportufrawplugin.cpp b/core/dplugins/rawimport/ufraw/rawimportufrawplugin.cpp new file mode 100644 index 0000000000..dab93a22c9 --- /dev/null +++ b/core/dplugins/rawimport/ufraw/rawimportufrawplugin.cpp @@ -0,0 +1,199 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : UFRaw raw import plugin. + * + * Copyright (C) 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 "rawimportufrawplugin.h" + +// Qt includes + +#include +#include +#include +#include +#include + +// KDE includes + +#include + +// Local includes + +#include "digikam_debug.h" + +namespace DigikamRawImportUFRawPlugin +{ + +UFRawRawImportPlugin::UFRawRawImportPlugin(QObject* const parent) + : DPluginRawImport(parent), + m_ufraw(nullptr), + m_tempFile(nullptr) +{ +} + +UFRawRawImportPlugin::~UFRawRawImportPlugin() +{ +} + +QString UFRawRawImportPlugin::name() const +{ + return QString::fromUtf8("Raw Import using UFRaw"); +} + +QString UFRawRawImportPlugin::iid() const +{ + return QLatin1String(DPLUGIN_IID); +} + +QIcon UFRawRawImportPlugin::icon() const +{ + return QIcon::fromTheme(QLatin1String("image-x-adobe-dng")); +} + +QString UFRawRawImportPlugin::description() const +{ + return QString::fromUtf8("A RAW import plugin based on UFRaw"); +} + +QString UFRawRawImportPlugin::details() const +{ + return QString::fromUtf8("

This RAW Import plugin use UFRaw tool to pre-process file in Image Editor.

"); +} + +QList UFRawRawImportPlugin::authors() const +{ + return QList() + << DPluginAuthor(QString::fromUtf8("Gilles Caulier"), + QString::fromUtf8("caulier dot gilles at gmail dot com"), + QString::fromUtf8("(C) 2019")) + ; +} + +void UFRawRawImportPlugin::setup(QObject* const /*parent*/) +{ + // Nothing to do +} + +bool UFRawRawImportPlugin::run(const QString& filePath, const DRawDecoding& /*def*/) +{ + m_fileInfo = QFileInfo(filePath); + m_props = LoadingDescription(m_fileInfo.filePath(), LoadingDescription::ConvertForEditor); + m_decoded = DImg(); + + delete m_tempFile; + + m_tempFile = new QTemporaryFile(); + m_tempFile->open(); + + m_ufraw = new QProcess(this); + m_ufraw->setProcessChannelMode(QProcess::MergedChannels); + m_ufraw->setWorkingDirectory(m_fileInfo.path()); + + connect(m_ufraw, SIGNAL(errorOccurred(QProcess::ProcessError)), + this, SLOT(slotErrorOccurred(QProcess::ProcessError))); + + connect(m_ufraw, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); + + connect(m_ufraw, SIGNAL(readyRead()), + this, SLOT(slotProcessReadyRead())); + + // -------- + + m_fileInfo = QFileInfo(filePath); + + m_ufraw->setProgram(QLatin1String("ufraw")); + m_ufraw->setArguments(QStringList() << QLatin1String("--out-depth=16") // 16 bits per color per pixels + << QLatin1String("--out-type=png") // PNG output (TIFF output generate multi-layers file) + << QLatin1String("--overwrite") // Overwrite target temporay file + << QString::fromUtf8("--output=%1").arg(m_tempFile->fileName()) // Output file + << filePath); // Input file + + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw arguments:" << m_ufraw->arguments(); + + m_ufraw->start(); + + return true; +} + +void UFRawRawImportPlugin::slotErrorOccurred(QProcess::ProcessError error) +{ + switch (error) + { + case QProcess::FailedToStart: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process has failed to start"; + break; + case QProcess::Crashed: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process has crashed"; + break; + case QProcess::Timedout: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process time-out"; + break; + case QProcess::WriteError: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process write error"; + break; + case QProcess::ReadError: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process read error"; + break; + default: + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: Process error unknown"; + break; + } +} + +void UFRawRawImportPlugin::slotProcessFinished(int code, QProcess::ExitStatus status) +{ + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw :: return code:" << code << ":: Exit status:" << status; + + m_decoded = DImg(m_tempFile->fileName()); + + if (m_decoded.isNull()) + { + QString message = i18n("Error to import RAW image with UFRaw\nClose this dialog to load RAW image with native import tool"); + QMessageBox::information(0, qApp->applicationName(), message); + + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is null! Load with Native tool..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + emit signalLoadRaw(m_props); + } + else + { + qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is not null..."; + qCDebug(DIGIKAM_GENERAL_LOG) << m_props.filePath; + m_props = LoadingDescription(m_tempFile->fileName(), LoadingDescription::ConvertForEditor); + emit signalDecodedImage(m_props, m_decoded); + } + + delete m_tempFile; + m_tempFile = nullptr; +} + +void UFRawRawImportPlugin::slotProcessReadyRead() +{ + QByteArray data = m_ufraw->readAllStandardError(); + QStringList lines = QString::fromUtf8(data).split(QLatin1Char('\n'), QString::SkipEmptyParts); + + foreach (const QString& one, lines) + { + qCDebug(DIGIKAM_GENERAL_LOG) << "UFRaw ::" << one; + } +} + +} // namespace DigikamRawImportUFRawPlugin diff --git a/core/dplugins/rawimport/ufraw/rawimportufrawplugin.h b/core/dplugins/rawimport/ufraw/rawimportufrawplugin.h new file mode 100644 index 0000000000..10925d6853 --- /dev/null +++ b/core/dplugins/rawimport/ufraw/rawimportufrawplugin.h @@ -0,0 +1,84 @@ +/* ============================================================ + * + * This file is a part of digiKam project + * https://www.digikam.org + * + * Date : 2019-09-18 + * Description : UFRaw raw import plugin. + * + * Copyright (C) 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_UFRAW_RAW_IMPORT_PLUGIN_H +#define DIGIKAM_UFRAW_RAW_IMPORT_PLUGIN_H + +// Qt includes + +#include +#include +#include + +// Local includes + +#include "dpluginrawimport.h" +#include "dimg.h" +#include "loadingdescription.h" + +#define DPLUGIN_IID "org.kde.digikam.plugin.rawimport.UFRaw" + +using namespace Digikam; + +namespace DigikamRawImportUFRawPlugin +{ + +class UFRawRawImportPlugin : public DPluginRawImport +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID DPLUGIN_IID) + Q_INTERFACES(Digikam::DPluginRawImport) + +public: + + explicit UFRawRawImportPlugin(QObject* const parent = nullptr); + ~UFRawRawImportPlugin(); + + QString name() const override; + QString iid() const override; + QIcon icon() const override; + QString details() const override; + QString description() const override; + QList authors() const override; + + void setup(QObject* const) override; + + bool run(const QString& filePath, const DRawDecoding& def); + +private Q_SLOTS: + + void slotErrorOccurred(QProcess::ProcessError); + void slotProcessFinished(int, QProcess::ExitStatus); + void slotProcessReadyRead(); + +private: + + QProcess* m_ufraw; + DImg m_decoded; + LoadingDescription m_props; + QFileInfo m_fileInfo; + QTemporaryFile* m_tempFile; +}; + +} // namespace DigikamRawImportUFRawPlugin + +#endif // DIGIKAM_UFRAW_RAW_IMPORT_PLUGIN_H diff --git a/core/libs/tags/manager/models/tagmngrlistitem.cpp b/core/libs/tags/manager/models/tagmngrlistitem.cpp index 2be1e23247..d288402bee 100644 --- a/core/libs/tags/manager/models/tagmngrlistitem.cpp +++ b/core/libs/tags/manager/models/tagmngrlistitem.cpp @@ -1,208 +1,213 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : List View Item for List View Model * * Copyright (C) 2013 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. * * ============================================================ */ #include "tagmngrlistitem.h" // KDE includes #include // Local includes #include "digikam_debug.h" #include "albummanager.h" #include "album.h" namespace Digikam { class Q_DECL_HIDDEN ListItem::Private { public: explicit Private() { parentItem = nullptr; } QList childItems; + QList toDelItems; QList itemData; QList tagIds; - QList tagsToDel; ListItem* parentItem; }; ListItem::ListItem(QList& data, ListItem* const parent) : d(new Private()) { d->parentItem = parent; d->itemData.append(data); data.removeFirst(); foreach (const QVariant& val, data) { d->tagIds.append(val.toInt()); } } ListItem::~ListItem() { qDeleteAll(d->childItems); + qDeleteAll(d->toDelItems); delete d; } void ListItem::deleteChild(ListItem* const item) { - d->childItems.removeOne(item); + int row = d->childItems.indexOf(item); + + if (row != -1) + deleteChild(row); } QList ListItem::allChildren() const { return d->childItems; } QList ListItem::getTagIds() const { return d->tagIds; } void ListItem::appendChild(ListItem* const item) { d->childItems.append(item); } void ListItem::removeTagId(int tagId) { d->tagIds.removeOne(tagId); } ListItem* ListItem::child(int row) const { return d->childItems.value(row); } int ListItem::childCount() const { return d->childItems.count(); } void ListItem::deleteChild(int row) { - return d->childItems.removeAt(row); + d->toDelItems << d->childItems.takeAt(row); } void ListItem::removeAll() { + d->toDelItems << d->childItems; d->childItems.clear(); } void ListItem::appendList(const QList& items) { d->childItems.append(items); } int ListItem::columnCount() const { return d->itemData.count(); } QVariant ListItem::data(int role) const { switch(role) { case Qt::DisplayRole: case Qt::ToolTipRole: { QString display; foreach (int tagId, d->tagIds) { TAlbum* const album = AlbumManager::instance()->findTAlbum(tagId); if (!album) { continue; } display.append(album->title()+ QLatin1String(", ")); if (role == Qt::DisplayRole && display.size() > 30) break; } if (display.isEmpty()) display.append(i18n("All Tags")); else display.remove(display.size()-2, 2); return QVariant(display); } default: { return QVariant(); } } } void ListItem::setData(const QList& data) { d->itemData = data; } ListItem* ListItem::parent() const { return d->parentItem; } int ListItem::row() const { if (d->parentItem) { return d->parentItem->allChildren().indexOf(const_cast(this)); } return 0; } ListItem* ListItem::containsItem(ListItem* const item) const { // We need to compare items and not pointers for (int it = 0 ; it < d->childItems.size() ; ++it) { if (item->equal(d->childItems.at(it))) { return d->childItems.at(it); } } return nullptr; } bool ListItem::equal(ListItem* const item) const { - return (this->d->tagIds) == (item->getTagIds()); + return (d->tagIds == item->getTagIds()); } } // namespace Digikam diff --git a/core/libs/tags/manager/models/tagmngrlistmodel.cpp b/core/libs/tags/manager/models/tagmngrlistmodel.cpp index 57e24f3fcf..418f01e1b2 100644 --- a/core/libs/tags/manager/models/tagmngrlistmodel.cpp +++ b/core/libs/tags/manager/models/tagmngrlistmodel.cpp @@ -1,322 +1,322 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : List View Model with support for mime data and drag-n-drop * * Copyright (C) 2013 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. * * ============================================================ */ #include "tagmngrlistmodel.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "tagmngrlistitem.h" namespace Digikam { class Q_DECL_HIDDEN TagMngrListModel::Private { public: explicit Private() { rootItem = nullptr; } ListItem* rootItem; QList dragNewSelection; }; TagMngrListModel::TagMngrListModel(QObject* const parent) : QAbstractItemModel(parent), d(new Private()) { QList rootData; rootData << QLatin1String("Quick List"); d->rootItem = new ListItem(rootData); } TagMngrListModel::~TagMngrListModel() { delete d->rootItem; delete d; } ListItem* TagMngrListModel::addItem(QList values) { emit layoutAboutToBeChanged(); ListItem* const item = new ListItem(values, d->rootItem); /** containsItem will return a valid pointer if item with the same * values is already added to it's children list. */ ListItem* const existingItem = d->rootItem->containsItem(item); if (!existingItem) { d->rootItem->appendChild(item); emit layoutChanged(); return item; } else { delete item; return existingItem; } } QList TagMngrListModel::allItems() const { return d->rootItem->allChildren(); } QList TagMngrListModel::getDragNewSelection() const { return d->dragNewSelection; } void TagMngrListModel::deleteItem(ListItem* const item) { if (!item) return; emit layoutAboutToBeChanged(); d->rootItem->deleteChild(item); emit layoutChanged(); } int TagMngrListModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 1; } Qt::DropActions TagMngrListModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList TagMngrListModel::mimeTypes() const { QStringList types; types << QLatin1String("application/vnd.text.list"); return types; } bool TagMngrListModel::setData(const QModelIndex& index, const QVariant& value, int role) { Q_UNUSED(role); ListItem* const parent = static_cast(index.internalPointer()); if (!parent) { qCDebug(DIGIKAM_GENERAL_LOG) << "No node found"; return false; } QList itemDa; itemDa << value; parent->appendChild(new ListItem(itemDa,parent)); return true; } QMimeData* TagMngrListModel::mimeData(const QModelIndexList& indexes) const { QMimeData* const mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - foreach(const QModelIndex& index, indexes) + foreach (const QModelIndex& index, indexes) { if (index.isValid()) { stream << index.row(); } } mimeData->setData(QLatin1String("application/vnd.text.list"), encodedData); return mimeData; } bool TagMngrListModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { Q_UNUSED(column); Q_UNUSED(parent); if (action == Qt::IgnoreAction) return true; if (!(data->hasFormat(QLatin1String("application/vnd.text.list")))) return false; QByteArray encodedData = data->data(QLatin1String("application/vnd.text.list")); QDataStream stream(&encodedData, QIODevice::ReadOnly); QList newItems; QList finalItems; QList toRemove; int itemPoz; int temp = 0; while (!stream.atEnd()) { stream >> itemPoz; newItems << d->rootItem->child(itemPoz); if (itemPoz < row) { - temp++; + ++temp; } toRemove.append(itemPoz); } row -= temp; emit layoutAboutToBeChanged(); for (QList::iterator itr = toRemove.end() -1 ; itr != toRemove.begin() -1 ; --itr) { d->rootItem->deleteChild(*itr); } emit layoutChanged(); for (int it = 0 ; it < d->rootItem->childCount() ; ++it) { finalItems.append(d->rootItem->child(it)); if (it == row) { finalItems.append(newItems); /** After drag-n-drop selection is messed up, store the interval were * new items are and TagsMngrListView will update selection */ d->dragNewSelection.clear(); d->dragNewSelection << row; d->dragNewSelection << row + newItems.size(); } } d->rootItem->removeAll(); d->rootItem->appendList(finalItems); emit layoutChanged(); return true; } QVariant TagMngrListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (role == Qt::SizeHintRole) return QSize(30,30); if (role == Qt::TextAlignmentRole) return Qt::AlignCenter; ListItem* const item = static_cast(index.internalPointer()); return item->data(role); } Qt::ItemFlags TagMngrListModel::flags(const QModelIndex& index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } QVariant TagMngrListModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return QVariant(i18n("Quick Access List")); return QVariant(); } QModelIndex TagMngrListModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); ListItem* parentItem = nullptr; if (!parent.isValid()) parentItem = d->rootItem; else parentItem = static_cast(parent.internalPointer()); ListItem* const childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex TagMngrListModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); ListItem* const childItem = static_cast(index.internalPointer()); ListItem* const parentItem = childItem->parent(); if (parentItem == d->rootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } int TagMngrListModel::rowCount(const QModelIndex &parent) const { ListItem* parentItem = nullptr; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = d->rootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } } // namespace Digikam diff --git a/core/libs/tags/manager/models/tagmngrlistview.cpp b/core/libs/tags/manager/models/tagmngrlistview.cpp index 7e9d3509b1..4684e886a7 100644 --- a/core/libs/tags/manager/models/tagmngrlistview.cpp +++ b/core/libs/tags/manager/models/tagmngrlistview.cpp @@ -1,182 +1,182 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-22 * Description : Reimplemented QListView for Tags Manager, with support for * drag-n-drop * * Copyright (C) 2013 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. * * ============================================================ */ #include "tagmngrlistview.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagmngrlistmodel.h" #include "tagmngrlistitem.h" #include "taglist.h" namespace Digikam { TagMngrListView::TagMngrListView(QWidget* const parent) : QTreeView(parent) { setRootIsDecorated(false); setAlternatingRowColors(true); } void TagMngrListView::startDrag(Qt::DropActions supportedActions) { QModelIndexList list = selectionModel()->selectedIndexes(); TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } QMimeData* const data = tagmodel->mimeData(list); if (!data) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no data obtained!"; return; } QDrag* const drag = new QDrag(this); drag->setMimeData(data); drag->exec(supportedActions, Qt::IgnoreAction); } QModelIndexList TagMngrListView::mySelectedIndexes() { return selectedIndexes(); } void TagMngrListView::dropEvent(QDropEvent* e) { QModelIndex index = indexVisuallyAt(e->pos()); TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } tagmodel->dropMimeData(e->mimeData(), e->dropAction(), index.row(), index.column(), index.parent()); QList toSel = tagmodel->getDragNewSelection(); if (toSel.size() != 2) { return; } QItemSelectionModel* const model = selectionModel(); model->clearSelection(); setCurrentIndex(tagmodel->index(toSel.first()+1, 0)); for (int it = toSel.first()+1 ; it <= toSel.last() ; ++it) { model->select(tagmodel->index(it, 0), model->Select); } } QModelIndex TagMngrListView::indexVisuallyAt(const QPoint& p) { if (viewport()->rect().contains(p)) { QModelIndex index = indexAt(p); if (index.isValid() && visualRect(index).contains(p)) { return index; } } return QModelIndex(); } void TagMngrListView::contextMenuEvent(QContextMenuEvent* event) { Q_UNUSED(event); QMenu popmenu(this); ContextMenuHelper cmhelper(&popmenu); TagList* const tagList = dynamic_cast(parent()); if (!tagList) { return; } QAction* const delAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Selected from List"), this); cmhelper.addAction(delAction, tagList, SLOT(slotDeleteSelected()), false); QModelIndexList sel = selectionModel()->selectedIndexes(); if (sel.size() == 1 && sel.first().row() == 0) delAction->setDisabled(true); cmhelper.exec(QCursor::pos()); } void TagMngrListView::slotDeleteSelected() { QModelIndexList sel = selectionModel()->selectedIndexes(); if (sel.isEmpty()) return; TagMngrListModel* const tagmodel = dynamic_cast(model()); if (!tagmodel) { qCDebug(DIGIKAM_GENERAL_LOG) << "Error! no model available!"; return; } - foreach(const QModelIndex& index, sel) + foreach (const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); tagmodel->deleteItem(item); } } } // namespace Digikam diff --git a/core/libs/tags/manager/taglist.cpp b/core/libs/tags/manager/taglist.cpp index 9080c6ec79..6155c702d8 100644 --- a/core/libs/tags/manager/taglist.cpp +++ b/core/libs/tags/manager/taglist.cpp @@ -1,292 +1,292 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-31 * Description : Tag List implementation as Quick Access for various * subtrees in Tag Manager * * Copyright (C) 2013 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. * * ============================================================ */ #include "taglist.h" // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "albumtreeview.h" #include "tagmngrtreeview.h" #include "tagmngrlistmodel.h" #include "tagmngrlistview.h" #include "tagmngrlistitem.h" namespace Digikam { class Q_DECL_HIDDEN TagList::Private { public: explicit Private() { addButton = nullptr; tagList = nullptr; tagListModel = nullptr; treeView = nullptr; } QPushButton* addButton; TagMngrListView* tagList; TagMngrListModel* tagListModel; TagMngrTreeView* treeView; QMap > tagMap; }; TagList::TagList(TagMngrTreeView* const treeView, QWidget* const parent) - : QWidget(parent), - d(new Private()) + : QWidget(parent), + d(new Private()) { d->treeView = treeView; QVBoxLayout* const layout = new QVBoxLayout(); d->addButton = new QPushButton(i18n("Add to List")); d->addButton->setToolTip(i18n("Add selected tags to Quick Access List")); d->tagList = new TagMngrListView(this); d->tagListModel = new TagMngrListModel(this); d->tagList->setModel(d->tagListModel); d->tagList->setSelectionMode(QAbstractItemView::ExtendedSelection); d->tagList->setDragEnabled(true); d->tagList->setAcceptDrops(true); d->tagList->setDropIndicatorShown(true); layout->addWidget(d->addButton); layout->addWidget(d->tagList); connect(d->addButton, SIGNAL(clicked()), this, SLOT(slotAddPressed())); connect(d->tagList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)), this, SLOT(slotTagDeleted(Album*))); restoreSettings(); this->setLayout(layout); } TagList::~TagList() { delete d; } void TagList::saveSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); conf.deleteGroup(QLatin1String("List Content")); KConfigGroup group = conf.group(QLatin1String("List Content")); QList currentItems = d->tagListModel->allItems(); group.writeEntry(QLatin1String("Size"), currentItems.count()-1); for (int it = 1 ; it < currentItems.size() ; ++it) { QList ids = currentItems.at(it)->getTagIds(); QString saveData; for (int jt = 0 ; jt < ids.size() ; ++jt) { saveData.append(QString::number(ids.at(jt)) + QLatin1Char(' ')); } group.writeEntry(QString::fromUtf8("item%1").arg(it-1), saveData); } } void TagList::restoreSettings() { KConfig conf(QLatin1String("digikam_tagsmanagerrc")); KConfigGroup group = conf.group(QLatin1String("List Content")); QStringList items; int size = group.readEntry(QLatin1String("Size"), -1); /** * If config is empty add generic All Tags */ d->tagListModel->addItem(QList() << QBrush(Qt::cyan, Qt::Dense2Pattern)); if (size == 0 || size < 0) { return; } for (int it = 0 ; it < size ; ++it) { QString data = group.readEntry(QString::fromUtf8("item%1").arg(it), ""); if (data.isEmpty()) continue; QStringList ids = data.split(QLatin1Char(' '), QString::SkipEmptyParts); QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach (const QString& tagId, ids) { TAlbum* const item = AlbumManager::instance()->findTAlbum(tagId.toInt()); if (item) { itemData << item->id(); } } ListItem* const listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach (int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } /** "All Tags" item should be selected **/ QModelIndex rootIndex = d->tagList->model()->index(0, 0); d->tagList->setCurrentIndex(rootIndex); } void TagList::slotAddPressed() { QModelIndexList selected = d->treeView->selectionModel()->selectedIndexes(); if (selected.isEmpty()) { return; } QList itemData; itemData << QBrush(Qt::cyan, Qt::Dense2Pattern); foreach (const QModelIndex& index, selected) { TAlbum* const album = static_cast(d->treeView->albumForIndex(index)); itemData << album->id(); } ListItem* listItem = d->tagListModel->addItem(itemData); /** Use this map to find all List Items that contain specific tag * usually to remove deleted tag */ foreach (int tagId, listItem->getTagIds()) { d->tagMap[tagId].append(listItem); } } void TagList::slotSelectionChanged() { QModelIndexList indexList = d->tagList->mySelectedIndexes(); QSet mySet; foreach (const QModelIndex& index, indexList) { ListItem* const item = static_cast(index.internalPointer()); if (item->getTagIds().isEmpty()) { mySet.clear(); break; } foreach (int tagId, item->getTagIds()) { mySet.insert(tagId); } } TagsManagerFilterModel* const filterModel = d->treeView->getFilterModel(); filterModel->setQuickListTags(QList::fromSet(mySet)); } void TagList::slotTagDeleted(Album* album) { TAlbum* const talbum = dynamic_cast(album); if (!talbum) { return; } int delId = talbum->id(); QList items = d->tagMap[delId]; foreach (ListItem* const item, items) { item->removeTagId(delId); if (item->getTagIds().isEmpty()) { d->tagListModel->deleteItem(item); d->tagMap[delId].removeOne(item); d->treeView->getFilterModel()->setQuickListTags(QList()); } } } void TagList::slotDeleteSelected() { QModelIndexList sel = d->tagList->selectionModel()->selectedIndexes(); if (sel.isEmpty()) { return; } foreach (const QModelIndex& index, sel) { ListItem* const item = static_cast(index.internalPointer()); d->tagListModel->deleteItem(item); } d->tagList->selectionModel()->select(d->tagList->model()->index(0, 0), QItemSelectionModel::SelectCurrent); } void TagList::enableAddButton(bool value) { d->addButton->setEnabled(value); } } // namespace Digikam diff --git a/core/libs/tags/manager/tagmngrtreeview.cpp b/core/libs/tags/manager/tagmngrtreeview.cpp index f10fcf0718..aa4bd750ea 100644 --- a/core/libs/tags/manager/tagmngrtreeview.cpp +++ b/core/libs/tags/manager/tagmngrtreeview.cpp @@ -1,241 +1,242 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-08-05 * Description : Tag Manager Tree View derived from TagsFolderView to implement * a custom context menu and some batch view options, such as * expanding multiple items * * Copyright (C) 2013 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. * * ============================================================ */ #include "tagmngrtreeview.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "contextmenuhelper.h" #include "tagsmanager.h" namespace Digikam { class Q_DECL_HIDDEN TagMngrTreeView::Private { public: explicit Private() { tagMngr = nullptr; } TagsManager* tagMngr; }; TagMngrTreeView::TagMngrTreeView(TagsManager* const parent, TagModel* const model) - : TagFolderView(parent, model), d(new Private()) + : TagFolderView(parent, model), + d(new Private()) { d->tagMngr = parent; setAlbumFilterModel(new TagsManagerFilterModel(this), albumFilterModel()); setSelectAlbumOnClick(false); expand(albumFilterModel()->rootAlbumIndex()); } TagMngrTreeView::~TagMngrTreeView() { delete d; } void TagMngrTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndexList selectedItems = selectionModel()->selectedIndexes(); std::sort(selectedItems.begin(), selectedItems.end()); QList items; - foreach(const QModelIndex& mIndex, selectedItems) + foreach (const QModelIndex& mIndex, selectedItems) { TAlbum* const temp = static_cast(albumForIndex(mIndex)); items.append(temp); } /** * Append root tag if no nodes are selected */ if (items.isEmpty()) { QModelIndex root = model()->index(0, 0); items.append(static_cast(albumForIndex(root))); } QMenu popmenu(this); popmenu.addSection(contextMenuIcon(), contextMenuTitle()); ContextMenuHelper cmhelper(&popmenu); setContexMenuItems(cmhelper, items); QAction* const choice = cmhelper.exec(QCursor::pos()); Q_UNUSED(choice); Q_UNUSED(event); } void TagMngrTreeView::setAlbumFilterModel(TagsManagerFilterModel* const filteredModel, CheckableAlbumFilterModel* const filterModel) { Q_UNUSED(filterModel); m_tfilteredModel = filteredModel; albumFilterModel()->setSourceFilterModel(m_tfilteredModel); } void TagMngrTreeView::setContexMenuItems(ContextMenuHelper& cmh, const QList& albums) { bool isRoot = false; if (albums.size() == 1) { TAlbum* const tag = dynamic_cast (albums.first()); if (!tag) { return; } if (tag->isRoot()) { isRoot = true; } cmh.addActionNewTag(tagModificationHelper(), tag); } if (!isRoot) { cmh.addActionDeleteTags(tagModificationHelper(), albums); } else { /** This is a dummy action, delete is disable for root tag **/ QAction* deleteTagsAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Tags"), this); cmh.addAction(deleteTagsAction); deleteTagsAction->setEnabled(false); } cmh.addSeparator(); QAction* const titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit Tag Title"), this); titleEdit->setShortcut(QKeySequence(Qt::Key_F2)); QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")), i18n("Invert Selection"), this); QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Tag Tree"), this); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")), i18n("Remove Tag from Images"), this); cmh.addAction(titleEdit, d->tagMngr, SLOT(slotEditTagTitle()), false); cmh.addAction(resetIcon, d->tagMngr, SLOT(slotResetTagIcon()), false); cmh.addAction(invSel, d->tagMngr, SLOT(slotInvertSel()), false); cmh.addAction(expandTree, this, SLOT(slotExpandTree()), false); cmh.addAction(expandSel, this , SLOT(slotExpandSelected()), false); cmh.addAction(delTagFromImg, d->tagMngr, SLOT(slotRemoveTagsFromImgs()), false); if (isRoot) { titleEdit->setEnabled(false); resetIcon->setEnabled(false); delTagFromImg->setEnabled(false); } if (albums.size() != 1) { titleEdit->setEnabled(false); } } void TagMngrTreeView::slotExpandSelected() { QModelIndexList list = selectionModel()->selectedIndexes(); - foreach(const QModelIndex& index, list) + foreach (const QModelIndex& index, list) { expand(index); } } void TagMngrTreeView::slotExpandTree() { QModelIndex root = model()->index(0, 0); QItemSelectionModel* const model = selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; greyNodes.append(root); while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!current.isValid()) { continue; } if (isExpanded(current)) { int it = 0; QModelIndex child = current.model()->index(it++, 0, current); while (child.isValid()) { if (isExpanded(child)) { greyNodes.enqueue(child); } else { expand(child); } child = current.model()->index(it++, 0, current); } } else { expand(current); } } } } // namespace Digikam diff --git a/core/libs/tags/manager/tagsmanager.cpp b/core/libs/tags/manager/tagsmanager.cpp index bad7d791c3..d738e52e54 100644 --- a/core/libs/tags/manager/tagsmanager.cpp +++ b/core/libs/tags/manager/tagsmanager.cpp @@ -1,1010 +1,1011 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-03 * Description : Tag Manager main class * * Copyright (C) 2013 by Veaceslav Munteanu * Copyright (C) 2014 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 "tagsmanager.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "digikam_debug.h" #include "digikam_config.h" #include "dmessagebox.h" #include "tagpropwidget.h" #include "tagmngrtreeview.h" #include "taglist.h" #include "tagfolderview.h" #include "ddragobjects.h" #include "searchtextbar.h" #include "tageditdlg.h" #include "coredb.h" #include "sidebar.h" #include "dlogoaction.h" #include "metadatasynchronizer.h" #include "fileactionmngr.h" #include "metaenginesettings.h" namespace Digikam { QPointer TagsManager::internalPtr = QPointer(); class Q_DECL_HIDDEN TagsManager::Private { public: explicit Private() { tagPixmap = nullptr; searchBar = nullptr; splitter = nullptr; treeWindow = nullptr; mainToolbar = nullptr; rightToolBar = nullptr; organizeAction = nullptr; syncexportAction = nullptr; tagProperties = nullptr; addAction = nullptr; delAction = nullptr; titleEdit = nullptr; listView = nullptr; tagPropWidget = nullptr; tagMngrView = nullptr; tagModel = nullptr; tagPropVisible = false; } TagMngrTreeView* tagMngrView; QLabel* tagPixmap; SearchTextBar* searchBar; QSplitter* splitter; KMainWindow* treeWindow; KToolBar* mainToolbar; DMultiTabBar* rightToolBar; QMenu* organizeAction; QMenu* syncexportAction; QAction* tagProperties; QAction* addAction; QAction* delAction; QAction* titleEdit; /** Options unavailable for root tag **/ QList rootDisabledOptions; TagList* listView; TagPropWidget* tagPropWidget; TagModel* tagModel; bool tagPropVisible; }; TagsManager::TagsManager() : KMainWindow(nullptr), StateSavingObject(this), d(new Private()) { setObjectName(QLatin1String("Tags Manager")); d->tagModel = new TagModel(AbstractAlbumModel::IncludeRootAlbum, this); d->tagModel->setCheckable(false); setupUi(this); /*----------------------------Connects---------------------------*/ connect(d->tagMngrView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectionChanged())); connect(d->addAction, SIGNAL(triggered()), this, SLOT(slotAddAction())); connect(d->delAction, SIGNAL(triggered()), this, SLOT(slotDeleteAction())); d->tagMngrView->setCurrentIndex(d->tagMngrView->model()->index(0, 0)); StateSavingObject::loadState(); /** Set KMainWindow in center of the screen **/ QScreen* screen = qApp->primaryScreen(); if (QWidget* const widget = qApp->activeWindow()) { if (QWindow* const window = widget->windowHandle()) screen = window->screen(); } const int screenIndex = qMax(qApp->screens().indexOf(screen), 0); move(qApp->screens().at(screenIndex)->geometry().center() - rect().center()); } TagsManager::~TagsManager() { StateSavingObject::saveState(); delete d; } TagsManager* TagsManager::instance() { if (TagsManager::internalPtr.isNull()) { TagsManager::internalPtr = new TagsManager(); } return TagsManager::internalPtr; } void TagsManager::setupUi(KMainWindow* const dialog) { dialog->resize(972, 722); dialog->setWindowTitle(i18n("Tags Manager")); QHBoxLayout* const mainLayout = new QHBoxLayout(); d->tagPixmap = new QLabel(); d->tagPixmap->setText(QLatin1String("Tag Pixmap")); d->tagPixmap->setMaximumWidth(40); d->tagPixmap->setPixmap(QIcon::fromTheme(QLatin1String("tag")).pixmap(30, 30)); d->tagMngrView = new TagMngrTreeView(this, d->tagModel); d->tagMngrView->setConfigGroup(getConfigGroup()); d->searchBar = new SearchTextBar(this, QLatin1String("ItemIconViewTagSearchBar")); d->searchBar->setHighlightOnResult(true); d->searchBar->setModel(d->tagMngrView->filteredModel(), AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole); d->searchBar->setMaximumWidth(200); d->searchBar->setFilterModel(d->tagMngrView->albumFilterModel()); /** Tree Widget & Actions + Tag Properties sidebar **/ d->treeWindow = new KMainWindow(this); setupActions(); d->tagPropWidget = new TagPropWidget(this); d->listView = new TagList(d->tagMngrView, this); d->splitter = new QSplitter(Qt::Horizontal, this); d->splitter->addWidget(d->listView); d->splitter->addWidget(d->tagMngrView); d->splitter->addWidget(d->tagPropWidget); d->tagPropWidget->hide(); connect(d->tagPropWidget, SIGNAL(signalTitleEditReady()), this, SLOT(slotTitleEditReady())); d->splitter->setStretchFactor(0, 0); d->splitter->setStretchFactor(1, 1); d->splitter->setStretchFactor(2, 0); d->treeWindow->setCentralWidget(d->splitter); mainLayout->addWidget(d->treeWindow); mainLayout->addWidget(d->rightToolBar); QWidget* const centraW = new QWidget(this); centraW->setLayout(mainLayout); setCentralWidget(centraW); } void TagsManager::slotOpenProperties() { DMultiTabBarTab* const sender = dynamic_cast(QObject::sender()); if (sender->isChecked()) { d->tagPropWidget->show(); } else { d->tagPropWidget->hide(); } d->tagPropVisible = d->tagPropWidget->isVisible(); } void TagsManager::slotSelectionChanged() { QList selectedTags = d->tagMngrView->selectedTags(); if (selectedTags.isEmpty() || (selectedTags.size() == 1 && selectedTags.at(0)->isRoot())) { enableRootTagActions(false); d->listView->enableAddButton(false); } else { enableRootTagActions(true); d->listView->enableAddButton(true); d->titleEdit->setEnabled((selectedTags.size() == 1)); } d->tagPropWidget->slotSelectionChanged(selectedTags); } void TagsManager::slotItemChanged() { } void TagsManager::slotAddAction() { TAlbum* parent = d->tagMngrView->currentAlbum(); QString title; QString icon; QKeySequence ks; if (!parent) { parent = static_cast(d->tagMngrView->albumForIndex(d->tagMngrView->model()->index(0, 0))); } 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); } namespace { QString JoinTagNamesToList(const QStringList& stringList) { const QString joinedStringList = stringList.join(QLatin1String("', '")); return QLatin1Char('\'') + joinedStringList + QLatin1Char('\''); } } // namespace void TagsManager::slotDeleteAction() { const QModelIndexList selected = d->tagMngrView->selectionModel()->selectedIndexes(); QStringList tagNames; QStringList tagsWithChildren; QStringList tagsWithImages; QMultiMap sortedTags; foreach (const QModelIndex& index, selected) { if (!index.isValid()) { return; } TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index)); if (!t || t->isRoot()) { return; } AlbumPointer tag(t); tagNames.append(tag->title()); // find number of subtags int children = 0; AlbumIterator iter(tag); while (iter.current()) { ++children; ++iter; } if (children) { tagsWithChildren.append(tag->title()); } QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); if (!assignedItems.isEmpty()) { tagsWithImages.append(tag->title()); } /** * Tags must be deleted from children to parents, if we don't want * to step on invalid index. Use QMultiMap to order them by distance * to root tag */ Album* parent = t; int depth = 0; while (!parent->isRoot()) { parent = parent->parent(); depth++; } sortedTags.insert(depth, tag); } // ask for deletion of children if (!tagsWithChildren.isEmpty()) { const int result = QMessageBox::warning(this, qApp->applicationName(), i18ncp("%2 is a comma separated list of tags to be deleted.", "Tag %2 has one or more subtags. " "Deleting it will also delete " "the subtags. " "Do you want to continue?", "Tags %2 have one or more subtags. " "Deleting them will also delete " "the subtags. " "Do you want to continue?", tagsWithChildren.count(), JoinTagNamesToList(tagsWithChildren)), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } } QString message; if (!tagsWithImages.isEmpty()) { message = i18ncp("%2 is a comma separated list of tags to be deleted.", "Tag %2 is assigned to one or more items. " "Do you want to delete it?", "Tags %2 are assigned to one or more items. " "Do you want to delete them?", tagsWithImages.count(), JoinTagNamesToList(tagsWithImages)); } else { message = i18ncp("%2 is a comma separated list of tags to be deleted.", "Delete tag %2?", "Delete tags %2?", tagNames.count(), JoinTagNamesToList(tagNames)); } const int result = QMessageBox::warning(this, i18np("Delete tag", "Delete tags", tagNames.count()), message, QMessageBox::Yes | QMessageBox::Cancel); if (result == QMessageBox::Yes) { QMultiMap::iterator it; /** * QMultimap doesn't provide reverse iterator, -1 is required * because end() points after the last element */ for (it = sortedTags.end()-1 ; it != sortedTags.begin()-1 ; --it) { QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(it.value(), errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagsManager::slotEditTagTitle() { QList selectedTags = d->tagMngrView->selectedTags(); if (selectedTags.size() == 1 && !selectedTags.at(0)->isRoot()) { d->tagPropWidget->show(); d->tagPropWidget->slotFocusTitleEdit(); d->rightToolBar->tab(0)->setChecked(true); } } void TagsManager::slotTitleEditReady() { if (!d->tagPropVisible) { d->tagPropWidget->hide(); d->rightToolBar->tab(0)->setChecked(false); } d->tagMngrView->setFocus(); } void TagsManager::slotResetTagIcon() { QString errMsg; const QList selected = d->tagMngrView->selectedTagAlbums(); const QString icon = QLatin1String("tag"); for (QList::const_iterator it = selected.constBegin() ; it != selected.constEnd() ; ++it) { TAlbum* const tag = *it; if (tag) { if (!AlbumManager::instance()->updateTAlbumIcon(tag, icon, 0, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } } } void TagsManager::slotCreateTagAddr() { } void TagsManager::slotInvertSel() { QModelIndex root = d->tagMngrView->model()->index(0, 0); QItemSelectionModel* const model = d->tagMngrView->selectionModel(); QModelIndexList selected = model->selectedIndexes(); QQueue greyNodes; bool currentSet = false; greyNodes.append(root); model->clearSelection(); while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!(current.isValid())) { continue; } int it = 0; QModelIndex child = current.model()->index(it++, 0, current); while (child.isValid()) { if (!selected.contains(child)) { if (!currentSet) { /** * Must set a new current item when inverting selection * it should be done only once */ d->tagMngrView->setCurrentIndex(child); currentSet = true; } model->select(child, model->Select); } if (d->tagMngrView->isExpanded(child)) { greyNodes.enqueue(child); } child = current.model()->index(it++, 0, current); } } } void TagsManager::slotWriteToImg() { int result = QMessageBox::warning(this, qApp->applicationName(), i18n("digiKam will clean up tag metadata before setting " "tags from database.
You may lose tags if you did not " "read tags before (by calling Read Tags from Image).
" "Do you want to continue?
"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation can take long time " "depending on collection size.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(), MetadataSynchronizer::WriteFromDatabaseToFile); tool->setTagsOnly(true); tool->start(); } void TagsManager::slotReadFromImg() { int result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation can take long time " "depending on collection size.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } MetadataSynchronizer* const tool = new MetadataSynchronizer(AlbumList(), MetadataSynchronizer::ReadFromFileToDatabase); tool->setTagsOnly(true); tool->start(); } void TagsManager::slotWipeAll() { const int result = QMessageBox::warning(this, qApp->applicationName(), i18n("This operation will wipe all tags from database only.\n" "To apply changes to files, " "you must choose write metadata to file later.\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } /** Disable writing tags to images **/ MetaEngineSettings* const metaSettings = MetaEngineSettings::instance(); MetaEngineSettingsContainer backUpContainer = metaSettings->settings(); MetaEngineSettingsContainer newContainer = backUpContainer; bool settingsChanged = false; if (backUpContainer.saveTags == true || backUpContainer.saveFaceTags == true) { settingsChanged = true; newContainer.saveTags = false; newContainer.saveFaceTags = false; metaSettings->setSettings(newContainer); } AlbumPointerList tagList; const QModelIndex root = d->tagMngrView->model()->index(0, 0); int iter = 0; QModelIndex child = root.model()->index(iter++, 0, root); while (child.isValid()) { tagList << AlbumPointer(d->tagMngrView->albumForIndex(child)); child = root.model()->index(iter++, 0, root); } AlbumPointerList::iterator it; for (it = tagList.begin() ; it != tagList.end() ; ++it) { QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(*it, errMsg)) { QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg); } } /** Restore settings after tag deletion **/ if (settingsChanged) { metaSettings->setSettings(backUpContainer); } } void TagsManager::slotRemoveTagsFromImgs() { const QModelIndexList selList = d->tagMngrView->selectionModel()->selectedIndexes(); const int result = QMessageBox::warning(this, qApp->applicationName(), i18np("Do you really want to remove the selected tag from all images?", "Do you really want to remove the selected tags from all images?", selList.count()), QMessageBox::Yes | QMessageBox::Cancel); if (result != QMessageBox::Yes) { return; } foreach (const QModelIndex& index, selList) { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(index)); AlbumPointer tag(t); if (tag->isRoot()) { continue; } QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(tag->id()); ItemInfoList imgList(assignedItems); FileActionMngr::instance()->removeTag(imgList, tag->id()); } } void TagsManager::closeEvent(QCloseEvent* event) { d->listView->saveSettings(); KMainWindow::closeEvent(event); } void TagsManager::setupActions() { d->mainToolbar = new KToolBar(d->treeWindow, true); d->mainToolbar->layout()->setContentsMargins(QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin), QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin)); QWidgetAction* const pixMapAction = new QWidgetAction(this); pixMapAction->setDefaultWidget(d->tagPixmap); QWidgetAction* const searchAction = new QWidgetAction(this); searchAction->setDefaultWidget(d->searchBar); d->mainToolbar->addAction(pixMapAction); d->mainToolbar->addAction(searchAction); d->mainToolbar->addSeparator(); - d->addAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), QLatin1String(""), d->treeWindow); + d->addAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), + QLatin1String(""), d->treeWindow); - d->delAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), QLatin1String(""), d->treeWindow); + d->delAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), + QLatin1String(""), d->treeWindow); /** organize group **/ d->organizeAction = new QMenu(i18nc("@title:menu", "Organize"), this); d->organizeAction->setIcon(QIcon::fromTheme(QLatin1String("autocorrection"))); d->titleEdit = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit Tag Title"), this); d->titleEdit->setShortcut(QKeySequence(Qt::Key_F2)); QAction* const resetIcon = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Reset Tag Icon"), this); QAction* const createTagAddr = new QAction(QIcon::fromTheme(QLatin1String("tag-addressbook")), i18n("Create Tag from Address Book"), this); QAction* const invSel = new QAction(QIcon::fromTheme(QLatin1String("tag-reset")), i18n("Invert Selection"), this); QAction* const expandTree = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Tag Tree"), this); QAction* const expandSel = new QAction(QIcon::fromTheme(QLatin1String("format-indent-more")), i18n("Expand Selected Nodes"), this); QAction* const delTagFromImg = new QAction(QIcon::fromTheme(QLatin1String("tag-delete")), i18n("Remove Tag from Images"), this); QAction* const deleteUnused = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")), i18n("Delete Unassigned Tags"), this); /** Tool tips **/ setHelpText(d->addAction, i18n("Add new tag to current tag. " "Current tag is last clicked tag.")); setHelpText(d->delAction, i18n("Delete selected items. " "Also work with multiple items, " "but will not delete the root tag.")); setHelpText(d->titleEdit, i18n("Edit title from selected tag.")); setHelpText(resetIcon, i18n("Reset icon to selected tags. " "Works with multiple selection.")); setHelpText(invSel, i18n("Invert selection. " "Only visible items will be selected")); setHelpText(expandTree, i18n("Expand tag tree by one level")); setHelpText(expandSel, i18n("Selected items will be expanded")); setHelpText(delTagFromImg, i18n("Delete selected tag(s) from images. " "Works with multiple selection.")); setHelpText(deleteUnused, i18n("Delete all tags that are not assigned to images. " "Use with caution.")); connect(d->titleEdit, SIGNAL(triggered()), this, SLOT(slotEditTagTitle())); connect(resetIcon, SIGNAL(triggered()), this, SLOT(slotResetTagIcon())); connect(createTagAddr, SIGNAL(triggered()), this, SLOT(slotCreateTagAddr())); connect(invSel, SIGNAL(triggered()), this, SLOT(slotInvertSel())); connect(expandTree, SIGNAL(triggered()), d->tagMngrView, SLOT(slotExpandTree())); connect(expandSel, SIGNAL(triggered()), d->tagMngrView, SLOT(slotExpandSelected())); connect(delTagFromImg, SIGNAL(triggered()), this, SLOT(slotRemoveTagsFromImgs())); connect(deleteUnused, SIGNAL(triggered()), this, SLOT(slotRemoveNotAssignedTags())); d->organizeAction->addAction(d->titleEdit); d->organizeAction->addAction(resetIcon); d->organizeAction->addAction(createTagAddr); d->organizeAction->addAction(invSel); d->organizeAction->addAction(expandTree); d->organizeAction->addAction(expandSel); d->organizeAction->addAction(delTagFromImg); d->organizeAction->addAction(deleteUnused); /** Sync & Export Group **/ d->syncexportAction = new QMenu(i18n("Sync &Export"), this); d->syncexportAction->setIcon(QIcon::fromTheme(QLatin1String("network-server-database"))); QAction* const wrDbImg = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), - i18n("Write Tags from Database " - "to Image"), this); + i18n("Write Tags from Database to Image"), this); QAction* const readTags = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18n("Read Tags from Image"), this); QAction* const wipeAll = new QAction(QIcon::fromTheme(QLatin1String("draw-eraser")), i18n("Wipe all tags from Database only"), this); setHelpText(wrDbImg, i18n("Write Tags Metadata to Image.")); setHelpText(readTags, i18n("Read tags from Images into Database. " "Existing tags will not be affected")); setHelpText(wipeAll, i18n("Delete all tags from database only. Will not sync with files. " "Proceed with caution.")); connect(wrDbImg, SIGNAL(triggered()), this, SLOT(slotWriteToImg())); connect(readTags, SIGNAL(triggered()), this, SLOT(slotReadFromImg())); connect(wipeAll, SIGNAL(triggered()), this, SLOT(slotWipeAll())); d->syncexportAction->addAction(wrDbImg); d->syncexportAction->addAction(readTags); d->syncexportAction->addAction(wipeAll); d->mainToolbar->addAction(d->addAction); d->mainToolbar->addAction(d->delAction); d->mainToolbar->addAction(d->organizeAction->menuAction()); d->mainToolbar->addAction(d->syncexportAction->menuAction()); d->mainToolbar->addAction(new DLogoAction(this)); addToolBar(d->mainToolbar); /** * Right Toolbar with vertical properties button */ d->rightToolBar = new DMultiTabBar(Qt::RightEdge); d->rightToolBar->appendTab(QIcon::fromTheme(QLatin1String("tag-properties")) .pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)), 0, i18n("Tag Properties")); d->rightToolBar->setStyle(DMultiTabBar::AllIconsText); connect(d->rightToolBar->tab(0), SIGNAL(clicked()), this, SLOT(slotOpenProperties())); d->rootDisabledOptions.append(d->delAction); d->rootDisabledOptions.append(d->titleEdit); d->rootDisabledOptions.append(resetIcon); d->rootDisabledOptions.append(delTagFromImg); } // helper based on KAction::setHelpText -void TagsManager::setHelpText(QAction *action, const QString& text) +void TagsManager::setHelpText(QAction* const action, const QString& text) { action->setStatusTip(text); action->setToolTip(text); if (action->whatsThis().isEmpty()) { action->setWhatsThis(text); } } void TagsManager::enableRootTagActions(bool value) { foreach (QAction* const action, d->rootDisabledOptions) { if (value) action->setEnabled(true); else action->setEnabled(false); } } void TagsManager::doLoadState() { KConfigGroup group = getConfigGroup(); d->tagMngrView->doLoadState(); group.sync(); } void TagsManager::doSaveState() { KConfigGroup group = getConfigGroup(); d->tagMngrView->doSaveState(); group.sync(); } void TagsManager::slotRemoveNotAssignedTags() { const int result = DMessageBox::showContinueCancel(QMessageBox::Warning, this, i18n("Warning"), i18n("This option will remove all tags which\n" "are not assigned to any image.\n " "Do you want to continue?")); if (result != QMessageBox::Yes) { return; } QModelIndex root = d->tagMngrView->model()->index(0, 0); QQueue greyNodes; QList redNodes; QSet greenNodes; int iter = 0; while (root.model()->hasIndex(iter, 0, root)) { greyNodes.append(root.model()->index(iter++, 0, root)); } while (!greyNodes.isEmpty()) { QModelIndex current = greyNodes.dequeue(); if (!(current.isValid())) { continue; } if (current.model()->hasIndex(0, 0, current)) { // Add in the list int iterator = 0; while (current.model()->hasIndex(iterator, 0, current)) { greyNodes.append(current.model()->index(iterator++, 0, current)); } } else { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current)); if (t && !t->isRoot() && !t->isInternalTag()) { QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id()); if (assignedItems.isEmpty()) { redNodes.append(current); } else { QModelIndex tmp = current.parent(); while (tmp.isValid()) { greenNodes.insert(tmp); tmp = tmp.parent(); } } } } } QList toRemove; foreach (const QModelIndex& toDelete, redNodes) { QModelIndex current = toDelete; while (current.isValid() && !greenNodes.contains(current)) { TAlbum* const t = static_cast(d->tagMngrView->albumForIndex(current)); if (t && !t->isRoot() && !t->isInternalTag()) { QList assignedItems = CoreDbAccess().db()->getItemIDsInTag(t->id()); if (assignedItems.isEmpty() && !toRemove.contains(t->id())) { toRemove.append(t->id()); } else { break; } } current = current.parent(); } } foreach (int id, toRemove) { TAlbum* const talbum = AlbumManager::instance()->findTAlbum(id); if (!talbum) { continue; } qCDebug(DIGIKAM_GENERAL_LOG) << talbum->title(); QString errMsg; if (!AlbumManager::instance()->deleteTAlbum(talbum, errMsg)) { QMessageBox::critical(this, qApp->applicationName(), errMsg); return; } } QMessageBox::information(this, qApp->applicationName(), i18np("%1 unused tag were removed.", "%1 unused tags were removed.", toRemove.count())); } } // namespace Digikam diff --git a/core/libs/tags/manager/tagsmanager.h b/core/libs/tags/manager/tagsmanager.h index 9d47d8bafd..990d5e791d 100644 --- a/core/libs/tags/manager/tagsmanager.h +++ b/core/libs/tags/manager/tagsmanager.h @@ -1,181 +1,181 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 20013-07-03 * Description : Tag Manager main class * * Copyright (C) 2013 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. * * ============================================================ */ #ifndef DIGIKAM_TAGS_MANAGER_H #define DIGIKAM_TAGS_MANAGER_H // Qt includes #include // KDE includes #include // Local includes #include "statesavingobject.h" namespace Digikam { class TagModel; class TAlbum; class TagsManager : public KMainWindow, public StateSavingObject { Q_OBJECT public: explicit TagsManager(); ~TagsManager(); /** * @brief setupUi setup all gui elements for Tag Manager * @param Dialog parent dialog */ void setupUi(KMainWindow* const dialog); static QPointer internalPtr; static TagsManager* instance(); static bool isCreated() { return !(internalPtr.isNull()); } Q_SIGNALS: void signalSelectionChanged(TAlbum* album); private Q_SLOTS: /** * @brief slotOpenProperties - open tag properties option when * activating Tag Properties from right sidebar */ void slotOpenProperties(); /** * @brief slotSelectionChanged - update tag properties in tagPropWidget when * different item is selected */ void slotSelectionChanged(); /** * Not used yet */ void slotItemChanged(); /** * @brief slotAddAction - add new tag when addAction(+) is triggered */ void slotAddAction(); /** * @brief slotDeleteAction - delete tag/tags when delAction is triggered */ void slotDeleteAction(); /** * @brief slotResetTagIcon - connected to resetTagIcon action and * will reset icon to all selected tags */ void slotResetTagIcon(); /** * @brief slotEditTagTitle - view Tag Properties and set focus to title edit */ void slotEditTagTitle(); /** * @brief slotTitleEditReady - title edit from Tag Properties was return button pressed */ void slotTitleEditReady(); /** * @brief slotCreateTagAddr - connected to createTagAddr action and * will create tags from Addressbook */ void slotCreateTagAddr(); /** * @brief slotInvertSel - connected to invSel action and will * invert selection of current items */ void slotInvertSel(); /** * @brief slotWriteToImg - connected to wrDbImg action and will * write all metadata from database to images */ void slotWriteToImg(); /** * @brief slotReadFromImg - coonected to readTags action and will * reread all images metadata into database */ void slotReadFromImg(); /** * @brief slotWipeAll - connected to wipeAll action and will * wipe all tag related data from database * and reread from image's metadata */ void slotWipeAll(); /** * @brief slotRemoveTagsFromImg - will remove selected tags from all * images that have them. */ void slotRemoveTagsFromImgs(); /** * @brief slotRemoveNotAssignedTags - remove all tags that are not assigned to images */ void slotRemoveNotAssignedTags(); protected: void closeEvent(QCloseEvent* event) override; virtual void doLoadState() override; virtual void doSaveState() override; private: void setupActions(); /** * @brief enableRootTagActions - enable or disable options when only root * tag is selected */ - void setHelpText(QAction* action, const QString& text); + void setHelpText(QAction* const action, const QString& text); void enableRootTagActions(bool value); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_TAGS_MANAGER_H diff --git a/core/utilities/imageeditor/core/editorcore.cpp b/core/utilities/imageeditor/core/editorcore.cpp index 4235092e1c..f73a53ee29 100644 --- a/core/utilities/imageeditor/core/editorcore.cpp +++ b/core/utilities/imageeditor/core/editorcore.cpp @@ -1,874 +1,870 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2003-01-15 * Description : DImg interface for image editor * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2004-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 "editorcore.h" #include "editorcore_p.h" // C++ includes #include #include #include #include // Qt includes #include #include #include #include #include #include #include #include #include namespace Digikam { EditorCore* EditorCore::m_defaultInstance = nullptr; EditorCore* EditorCore::defaultInstance() { return m_defaultInstance; } void EditorCore::setDefaultInstance(EditorCore* const instance) { m_defaultInstance = instance; } EditorCore::EditorCore() : QObject(), d(new Private) { d->undoMan = new UndoManager(this); d->thread = new SharedLoadSaveThread; connect( d->thread, SIGNAL(signalImageLoaded(LoadingDescription,DImg)), this, SLOT(slotImageLoaded(LoadingDescription,DImg)) ); connect( d->thread, SIGNAL(signalImageSaved(QString,bool)), this, SLOT(slotImageSaved(QString,bool)) ); connect( d->thread, SIGNAL(signalLoadingProgress(LoadingDescription,float)), this, SLOT(slotLoadingProgress(LoadingDescription,float)) ); connect( d->thread, SIGNAL(signalSavingProgress(QString,float)), this, SLOT(slotSavingProgress(QString,float)) ); } EditorCore::~EditorCore() { delete d->undoMan; delete d->thread; delete d; if (m_defaultInstance == this) { m_defaultInstance = nullptr; } } void EditorCore::setDisplayingWidget(QWidget* const widget) { d->displayingWidget = widget; } void EditorCore::load(const QString& filePath, IOFileSettings* const iofileSettings) { LoadingDescription description(filePath, LoadingDescription::ConvertForEditor); if (DImg::fileFormat(filePath) == DImg::RAW) { if (EditorToolIface::editorToolIface() && iofileSettings->useRAWImport) { - foreach (DPlugin* const p, DPluginLoader::instance()->allPlugins()) + if (d->rawPlugin && (d->rawPlugin->iid() != iofileSettings->rawImportToolIid)) { - DPluginRawImport* const raw = dynamic_cast(p); + disconnect(d->rawPlugin, SIGNAL(signalDecodedImage(Digikam::LoadingDescription,Digikam::DImg)), + this, SLOT(slotLoadRawFromTool(Digikam::LoadingDescription,Digikam::DImg))); - if (raw && d->rawPlugin && (raw->iid() == iofileSettings->rawImportToolIid)) - { - if (d->rawPlugin != raw) - { - disconnect(d->rawPlugin, SIGNAL(signalDecodedImage(Digikam::LoadingDescription,Digikam::DImg)), - this, SLOT(slotLoadRawFromTool(Digikam::LoadingDescription,Digikam::DImg))); + disconnect(d->rawPlugin, SIGNAL(signalLoadRaw(Digikam::LoadingDescription)), + this, SLOT(slotLoadRaw(Digikam::LoadingDescription))); - disconnect(d->rawPlugin, SIGNAL(signalLoadRaw(Digikam::LoadingDescription)), - this, SLOT(slotLoadRaw(Digikam::LoadingDescription))); - - d->rawPlugin = nullptr; - } - else - { - break; - } - } + d->rawPlugin = nullptr; + } - if (raw && !d->rawPlugin) + if (!d->rawPlugin) + { + foreach (DPlugin* const p, DPluginLoader::instance()->allPlugins()) { - d->rawPlugin = raw; + DPluginRawImport* const raw = dynamic_cast(p); - connect(d->rawPlugin, SIGNAL(signalDecodedImage(Digikam::LoadingDescription,Digikam::DImg)), - this, SLOT(slotLoadRawFromTool(Digikam::LoadingDescription,Digikam::DImg))); + if (raw && (raw->iid() == iofileSettings->rawImportToolIid)) + { + d->rawPlugin = raw; - connect(d->rawPlugin, SIGNAL(signalLoadRaw(Digikam::LoadingDescription)), - this, SLOT(slotLoadRaw(Digikam::LoadingDescription))); + connect(d->rawPlugin, SIGNAL(signalDecodedImage(Digikam::LoadingDescription,Digikam::DImg)), + this, SLOT(slotLoadRawFromTool(Digikam::LoadingDescription,Digikam::DImg))); - break; + connect(d->rawPlugin, SIGNAL(signalLoadRaw(Digikam::LoadingDescription)), + this, SLOT(slotLoadRaw(Digikam::LoadingDescription))); + + break; + } } } if (d->rawPlugin) { d->rawPlugin->run(filePath, iofileSettings->rawDecodingSettings); d->thread->stopLoading(); return; } qCCritical(DIGIKAM_GENERAL_LOG) << "Cannot found Raw Import tool! This probably due to a wrong " "install of plugins. Load Raw file with default settings..."; } } d->load(description); } void EditorCore::slotLoadRawFromTool(const LoadingDescription& props, const DImg& img) { d->resetValues(); d->currentDescription = props; emit signalLoadingStarted(d->currentDescription.filePath); slotImageLoaded(d->currentDescription, img); EditorToolIface::editorToolIface()->unLoadTool(); //emit signalImageLoaded(d->currentDescription.filePath, true); } void EditorCore::slotLoadRaw(const LoadingDescription& props) { //qCDebug(DIGIKAM_GENERAL_LOG) << d->nextRawDescription.rawDecodingSettings; d->load(props); } void EditorCore::applyTransform(const IccTransform& transform) { if (!d->valid) { return; } d->currentDescription.postProcessingParameters.colorManagement = LoadingDescription::ApplyTransform; d->currentDescription.postProcessingParameters.setTransform(transform); d->loadCurrent(); if (EditorToolIface::editorToolIface()) { EditorToolIface::editorToolIface()->unLoadTool(); } } void EditorCore::restore() { LoadingDescription description = d->currentDescription; d->resetValues(); d->load(description); } void EditorCore::resetImage() { if (EditorToolIface::editorToolIface()) { EditorToolIface::editorToolIface()->unLoadTool(); } d->resetValues(); d->image.reset(); } void EditorCore::setICCSettings(const ICCSettingsContainer& cmSettings) { d->cmSettings = cmSettings; } ICCSettingsContainer EditorCore::getICCSettings() const { return d->cmSettings; } void EditorCore::setExposureSettings(ExposureSettingsContainer* const expoSettings) { d->expoSettings = expoSettings; } ExposureSettingsContainer* EditorCore::getExposureSettings() const { return d->expoSettings; } void EditorCore::slotImageLoaded(const LoadingDescription& loadingDescription, const DImg& img) { if (loadingDescription != d->currentDescription) { return; } // RAW tool active? Discard previous loaded image // Special case for Jenkins, no Editor window also no EditorToolIface // if (EditorToolIface::editorToolIface()) // { // EditorTool* const tool = EditorToolIface::editorToolIface()->currentTool(); // if (tool && tool->property("DPluginIId").toString().contains(QLatin1String("rawimport"))) // { // return; // } // } bool valRet = false; d->image = img; if (!d->image.isNull()) { d->valid = true; valRet = true; d->resolvedInitialHistory = d->image.getOriginalImageHistory(); d->resolvedInitialHistory.clearReferredImages(); // default empty, real values set by higher level // Raw files are already rotated properly by Raw engine. Only perform auto-rotation with non-RAW files. // We don't have a feedback from Raw engine about auto-rotated RAW file during decoding. // Setting rotatedOrFlipped to true will reset the exif flag on save (the data is then already rotated) if (d->image.detectedFormat() == DImg::RAW) { d->rotatedOrFlipped = true; } else if (d->exifOrient) { // Do not rotate twice if already rotated, e.g. for full size preview. QVariant attribute(d->image.attribute(QLatin1String("exifRotated"))); if (!attribute.isValid() || !attribute.toBool()) { d->rotatedOrFlipped = d->image.rotateAndFlip(LoadSaveThread::exifOrientation(d->image, loadingDescription.filePath)); } } // set after rotation d->origWidth = d->image.width(); d->origHeight = d->image.height(); d->width = d->origWidth; d->height = d->origHeight; d->image.setAttribute(QLatin1String("originalSize"), d->image.size()); } else { valRet = false; } emit signalImageLoaded(d->currentDescription.filePath, valRet); setModified(); } void EditorCore::setSoftProofingEnabled(bool enabled) { d->doSoftProofing = enabled; } bool EditorCore::softProofingEnabled() const { return d->doSoftProofing; } void EditorCore::slotLoadingProgress(const LoadingDescription& loadingDescription, float progress) { if (loadingDescription == d->currentDescription) { emit signalLoadingProgress(loadingDescription.filePath, progress); } } bool EditorCore::exifRotated() const { return d->rotatedOrFlipped; } void EditorCore::setExifOrient(bool exifOrient) { d->exifOrient = exifOrient; } void EditorCore::undo() { if (!d->undoMan->anyMoreUndo()) { emit signalUndoStateChanged(); return; } d->undoMan->undo(); emit signalUndoStateChanged(); } void EditorCore::redo() { if (!d->undoMan->anyMoreRedo()) { emit signalUndoStateChanged(); return; } d->undoMan->redo(); emit signalUndoStateChanged(); } void EditorCore::rollbackToOrigin() { d->undoMan->rollbackToOrigin(); emit signalUndoStateChanged(); } void EditorCore::saveAs(const QString& filePath, IOFileSettings* const iofileSettings, bool setExifOrientationTag, const QString& givenMimeType, const QString& intendedFilePath) { d->saveAs(filePath, iofileSettings, setExifOrientationTag, givenMimeType, VersionFileOperation(), intendedFilePath); } void EditorCore::saveAs(const QString& filePath, IOFileSettings* const iofileSettings, bool setExifOrientationTag, const QString& givenMimeType, const VersionFileOperation& op) { d->saveAs(filePath, iofileSettings, setExifOrientationTag, givenMimeType, op, op.saveFile.filePath()); } void EditorCore::slotImageSaved(const QString& filePath, bool success) { if (d->filesToSave.isEmpty() || d->filesToSave[d->currentFileToSave].filePath != filePath) { return; } Private::FileToSave& savedFile = d->filesToSave[d->currentFileToSave]; if (success) { if (savedFile.historyStep == -1) { // Note: We operate on a temp file here, so we cannot // add it as referred image yet. Done in addLastSavedToHistory LoadingDescription description(filePath, LoadingDescription::ConvertForEditor); d->currentDescription = description; } else { HistoryImageId id = savedFile.image.addAsReferredImage(filePath); // for all images following in history, we need to insert the now saved file at the right place for (int i = d->currentFileToSave + 1; i < d->filesToSave.size(); ++i) { d->filesToSave[i].image.insertAsReferredImage(savedFile.historyStep, id); } } } else { qCWarning(DIGIKAM_GENERAL_LOG) << "error saving image '" << QFile::encodeName(filePath).constData(); } d->currentFileToSave++; if (d->currentFileToSave == d->filesToSave.size()) { d->filesToSave.clear(); emit signalImageSaved(filePath, success); } else { d->saveNext(); } } void EditorCore::slotSavingProgress(const QString& filePath, float progress) { if (!d->filesToSave.isEmpty() && d->filesToSave.at(d->currentFileToSave).filePath == filePath) { emit signalSavingProgress(filePath, progress); } } void EditorCore::abortSaving() { // failure will be reported by a signal if (!d->filesToSave.isEmpty()) { d->thread->stopSaving(d->filesToSave.at(d->currentFileToSave).filePath); d->filesToSave.clear(); } } QString EditorCore::ensureHasCurrentUuid() const { /* * 1) An image is loaded. The DImgLoader adds the HistoryImageId of the loaded file as "Current" entry. * 2) The loaded image has no UUID (created by camera etc.). Higher level calls ensureHasCurrentUuid * before any saving is started * 3) We create a new UUID and add it to the image's history. When the new image is saved, * it references the original by UUID. Because we, here, do not touch the original, * it is out of scope to add the UUID to the original file's metadata. * Higher level is responsible for this. * 4) When the image is saved, DImg::updateMetadata will create a new UUID for the saved * image, which is then of course written to the newly saved file. */ if (!d->image.getItemHistory().currentReferredImage().hasUuid()) { // if there is no uuid in the image, we create one. QString uuid = QString::fromUtf8(d->image.createImageUniqueId()); d->image.addCurrentUniqueImageId(uuid); } return d->image.getItemHistory().currentReferredImage().uuid(); } void EditorCore::provideCurrentUuid(const QString& uuid) { // If the (original) image did not yet have a UUID, one is provided by higher level // Higher level decides how this UUID is stored; we don't touch the original here. if (!d->image.getItemHistory().currentReferredImage().hasUuid()) { d->image.addCurrentUniqueImageId(uuid); } } void EditorCore::setLastSaved(const QString& filePath) { if (getImageFilePath() == filePath) { // if the file was overwritten, a complete undo, to the state of original loading, // does not return to a real image anymore - it's overwritten d->undoMan->clearPreviousOriginData(); } // We cannot do it in slotImageSaved because we may operate on a temporary filePath. d->image.imageSavedAs(filePath); } void EditorCore::switchToLastSaved(const DImageHistory& resolvedCurrentHistory) { // Higher level wants to use the current DImg object to represent the file // it has previously been saved to. // setLastSaved shall have been called before. d->image.switchOriginToLastSaved(); if (resolvedCurrentHistory.isNull()) { d->resolvedInitialHistory = d->image.getOriginalImageHistory(); d->resolvedInitialHistory.clearReferredImages(); } else { d->resolvedInitialHistory = resolvedCurrentHistory; } setUndoManagerOrigin(); } void EditorCore::setHistoryIsBranch(bool isBranching) { // The first added step (on top of the initial history) will be marked as branch d->image.setHistoryBranchAfter(d->resolvedInitialHistory, isBranching); } void EditorCore::setModified() { emit signalModified(); emit signalUndoStateChanged(); } void EditorCore::readMetadataFromFile(const QString& file) { DMetadata meta(file); // This can overwrite metadata changes introduced by tools. // Currently, this is ProfileConversion and lensfun. // ProfileConversion's changes is redone when saving by DImgLoader. // Lensfun is not critical. // For a clean solution, we'd need to record a sort of metadata changeset in UndoMetadataContainer. d->image.setMetadata(meta.data()); // If we are editing, and someone else at the same time, there's nothing we can do. if (!d->undoMan->hasChanges()) { d->image.setItemHistory(DImageHistory::fromXml(meta.getItemHistory())); } } void EditorCore::clearUndoManager() { d->undoMan->clear(); d->undoMan->setOrigin(); emit signalUndoStateChanged(); } void EditorCore::setUndoManagerOrigin() { d->undoMan->setOrigin(); emit signalUndoStateChanged(); emit signalFileOriginChanged(getImageFilePath()); } bool EditorCore::isValid() const { return d->valid; } int EditorCore::width() const { return d->width; } int EditorCore::height() const { return d->height; } int EditorCore::origWidth() const { return d->origWidth; } int EditorCore::origHeight() const { return d->origHeight; } int EditorCore::bytesDepth() const { return d->image.bytesDepth(); } bool EditorCore::sixteenBit() const { return d->image.sixteenBit(); } bool EditorCore::hasAlpha() const { return d->image.hasAlpha(); } bool EditorCore::isReadOnly() const { if (d->image.isNull()) { return true; } else { return d->image.isReadOnly(); } } void EditorCore::setSelectedArea(const QRect& rect) { d->selX = rect.x(); d->selY = rect.y(); d->selW = rect.width(); d->selH = rect.height(); } QRect EditorCore::getSelectedArea() const { return (QRect(d->selX, d->selY, d->selW, d->selH)); } void EditorCore::zoom(double val) { d->zoom = val; d->width = (int)(d->origWidth * val); d->height = (int)(d->origHeight * val); } void EditorCore::rotate90() { d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate90)); } void EditorCore::rotate180() { d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate180)); } void EditorCore::rotate270() { d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate270)); } void EditorCore::flipHoriz() { d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::FlipHorizontally)); } void EditorCore::flipVert() { d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::FlipVertically)); } void EditorCore::crop(const QRect& rect) { d->applyBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Crop, rect), new UndoActionIrreversible(this, QLatin1String("Crop"))); } void EditorCore::convertDepth(int depth) { d->applyBuiltinFilter(DImgBuiltinFilter(depth == 32 ? DImgBuiltinFilter::ConvertTo8Bit : DImgBuiltinFilter::ConvertTo16Bit), new UndoActionIrreversible(this, QLatin1String("Convert Color Depth"))); } DImg* EditorCore::getImg() const { if (!d->image.isNull()) { return &d->image; } else { qCWarning(DIGIKAM_GENERAL_LOG) << "d->image is NULL"; return nullptr; } } DImageHistory EditorCore::getItemHistory() const { return d->image.getItemHistory(); } DImageHistory EditorCore::getInitialImageHistory() const { return d->image.getOriginalImageHistory(); } DImageHistory EditorCore::getImageHistoryOfFullRedo() const { return d->undoMan->getImageHistoryOfFullRedo(); } DImageHistory EditorCore::getResolvedInitialHistory() const { return d->resolvedInitialHistory; } void EditorCore::setResolvedInitialHistory(const DImageHistory& history) { d->resolvedInitialHistory = history; } void EditorCore::putImg(const QString& caller, const FilterAction& action, const DImg& img) { d->undoMan->addAction(new UndoActionIrreversible(this, caller)); d->putImageData(img.bits(), img.width(), img.height(), img.sixteenBit()); d->image.addFilterAction(action); setModified(); } void EditorCore::setUndoImg(const UndoMetadataContainer& c, const DImg& img) { // called from UndoManager d->putImageData(img.bits(), img.width(), img.height(), img.sixteenBit()); c.toImage(d->image); } void EditorCore::imageUndoChanged(const UndoMetadataContainer& c) { // called from UndoManager d->origWidth = d->image.width(); d->origHeight = d->image.height(); c.toImage(d->image); } void EditorCore::setFileOriginData(const QVariant& data) { d->image.setFileOriginData(data); emit signalFileOriginChanged(getImageFilePath()); } DImg EditorCore::getImgSelection() const { if (!d->selW || !d->selH) { return DImg(); } if (!d->image.isNull()) { DImg im = d->image.copy(d->selX, d->selY, d->selW, d->selH); im.detach(); return im; } return DImg(); } void EditorCore::putImgSelection(const QString& caller, const FilterAction& action, const DImg& img) { if (img.isNull() || d->image.isNull()) { return; } d->undoMan->addAction(new UndoActionIrreversible(this, caller)); d->image.bitBltImage(img.bits(), 0, 0, d->selW, d->selH, d->selX, d->selY, d->selW, d->selH, d->image.bytesDepth()); d->image.addFilterAction(action); setModified(); } void EditorCore::putIccProfile(const IccProfile& profile) { if (d->image.isNull()) { qCWarning(DIGIKAM_GENERAL_LOG) << "d->image is NULL"; return; } //qCDebug(DIGIKAM_GENERAL_LOG) << "Embedding profile: " << profile; d->image.setIccProfile(profile); setModified(); } QStringList EditorCore::getUndoHistory() const { return d->undoMan->getUndoHistory(); } QStringList EditorCore::getRedoHistory() const { return d->undoMan->getRedoHistory(); } int EditorCore::availableUndoSteps() const { return d->undoMan->availableUndoSteps(); } int EditorCore::availableRedoSteps() const { return d->undoMan->availableRedoSteps(); } IccProfile EditorCore::getEmbeddedICC() const { return d->image.getIccProfile(); } MetaEngineData EditorCore::getMetadata() const { return d->image.getMetadata(); } QString EditorCore::getImageFilePath() const { return d->image.originalFilePath(); } QString EditorCore::getImageFileName() const { return getImageFilePath().section(QLatin1Char('/'), -1); } QString EditorCore::getImageFormat() const { if (d->image.isNull()) { return QString(); } QString mimeType = d->image.format(); // It is a bug in the loader if format attribute is not given if (mimeType.isEmpty()) { qCWarning(DIGIKAM_GENERAL_LOG) << "DImg object does not contain attribute \"format\""; mimeType = QString::fromUtf8(QImageReader::imageFormat(getImageFilePath())); } return mimeType; } QPixmap EditorCore::convertToPixmap(DImg& img) const { QPixmap pix; if (d->cmSettings.enableCM && (d->cmSettings.useManagedView || d->doSoftProofing)) { // do not use d->monitorICCtrans here, because img may have a different embedded profile IccManager manager(img); IccTransform transform; if (d->doSoftProofing) { transform = manager.displaySoftProofingTransform(IccProfile(d->cmSettings.defaultProofProfile)); } else { transform = manager.displayTransform(); } pix = img.convertToPixmap(transform); } else { pix = img.convertToPixmap(); } // Show the Over/Under exposure pixels indicators if (d->expoSettings->underExposureIndicator || d->expoSettings->overExposureIndicator) { QPainter painter(&pix); QImage pureColorMask = img.pureColorMask(d->expoSettings); QPixmap pixMask = QPixmap::fromImage(pureColorMask); painter.drawPixmap(0, 0, pixMask, 0, 0, pixMask.width(), pixMask.height()); } return pix; } UndoState EditorCore::undoState() const { UndoState state; state.hasUndo = d->undoMan->anyMoreUndo(); state.hasRedo = d->undoMan->anyMoreRedo(); state.hasUndoableChanges = !d->undoMan->isAtOrigin(); // Includes the edit step performed by RAW import, which is not undoable state.hasChanges = d->undoMan->hasChanges(); return state; } } // namespace Digikam diff --git a/project/bundles/3rdparty/ext_qt/CMakeLists.txt b/project/bundles/3rdparty/ext_qt/CMakeLists.txt index 38c99af7d8..7e01873be6 100644 --- a/project/bundles/3rdparty/ext_qt/CMakeLists.txt +++ b/project/bundles/3rdparty/ext_qt/CMakeLists.txt @@ -1,97 +1,99 @@ # Script to build Qt for digiKam bundle. # # Copyright (c) 2015-2019, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # SET(EXTPREFIX_qt "${EXTPREFIX}") IF(NOT ENABLE_QTWEBENGINE) SET(DROP_QTWEBENGINE_DEPS -skip qtwebengine # No need Chromium browser support (QtWebkit instead) -skip qtwebchannel # QtWebChannel support ==> QWebEngine dependency -skip qtquickcontrols # QtQuick support ==> QWebEngine dependency ) ENDIF() ExternalProject_Add(ext_qt DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL https://download.qt.io/official_releases/qt/5.13/5.13.1/single/qt-everywhere-src-5.13.1.tar.xz URL_MD5 d66b1da335d0c25325fdf493e9044c38 + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-icu-hack.patch + # PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-appimage-support.patch && # ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/qt-appimage-glibc.patch CMAKE_ARGS -DOPENSSL_LIBS='-L${EXTPREFIX_qt}/lib -lssl -lcrypto' CONFIGURE_COMMAND /configure -prefix ${EXTPREFIX_qt} -verbose -release -opensource -confirm-license -sql-sqlite # Compile Sqlite SQL plugin -sql-mysql # Compile Mysql SQL plugin -fontconfig # -icu # International Component Unicode -system-freetype # Use system font rendering lib https://doc.qt.io/qt-5/qtgui-attribution-freetype.html -openssl-linked # hard link ssl libraries -nomake tests # Do not build test codes -nomake examples # Do not build basis example codes -no-compile-examples # Do not build extra example codes -no-qml-debug -no-mtdev -no-journald -no-syslog -no-tslib -no-directfb -no-linuxfb -no-libproxy -no-pch -qt-zlib -qt-pcre -qt-harfbuzz -qt-xcb -skip qt3d # 3D core -skip qtactiveqt # No need ActiveX support -skip qtandroidextras # For embeded devices only -skip qtcharts # No need data models charts support -skip qtconnectivity # For embeded devices only -skip qtdatavis3d # no need 3D data visualizations support -skip qtdoc # No need documentation -skip qtgamepad # No need gamepad hardware support. -skip qtgraphicaleffects # No need Advanced graphical effects in GUI -skip qtlocation # No need geolocation -skip qtlottie # No need Adobe QtQuick After Effect animations integration -skip qtmacextras # For MacOS devices only -skip qtmultimedia # No need multimedia support (replaced by QtAV+ffmpeg) -skip qtnetworkauth # No need network authentification support. -skip qtpurchasing # No need in-app purchase of products support -skip qtquickcontrols2 # QtQuick support for QML -skip qtremoteobjects # No need sharing QObject properties between processes support -skip qtscript # No need scripting (deprecated) -skip qtscxml # No need SCXML state machines support -skip qtsensors # For embeded devices only -skip qtserialbus # No need serial bus support -skip qtserialport # No need serial port support -skip qtspeech # No need speech synthesis support -skip qttranslations # No need translation tools. -skip qtvirtualkeyboard # No need virtual keyboard support -skip qtwayland # Specific to Linux -skip qtwebglplugin # No need browser OpenGL extention support -skip qtwebsockets # No need websocket support -skip qtwebview # QML extension for QWebEngine -skip qtwinextras # For Windows devices only ${DROP_QTWEBENGINE_DEPS} # Exemple of code to install only one Qt module for hacking purpose (aka qtbase here) #BUILD_COMMAND cd && $(MAKE) module-qtbase #INSTALL_COMMAND cd && $(MAKE) module-qtbase-install_subtargets UPDATE_COMMAND "" BUILD_IN_SOURCE 1 ALWAYS 0 ) diff --git a/project/bundles/3rdparty/ext_qt/qt-icu-hack.patch b/project/bundles/3rdparty/ext_qt/qt-icu-hack.patch new file mode 100644 index 0000000000..4c62a403e9 --- /dev/null +++ b/project/bundles/3rdparty/ext_qt/qt-icu-hack.patch @@ -0,0 +1,13 @@ +diff --git a/qtbase/src/corelib/tools/qcollator_icu.cpp b/qtbase/src/corelib/tools/qcollator_icu.cpp +index ab45b9a1a1..dd278b768a 100644 +--- a/qtbase/src/corelib/tools/qcollator_icu.cpp ++++ b/qtbase/src/corelib/tools/qcollator_icu.cpp +@@ -62,7 +62,7 @@ void QCollatorPrivate::init() + QByteArray name = QLocalePrivate::get(locale)->bcp47Name('_'); + collator = ucol_open(name.constData(), &status); + if (U_FAILURE(status)) { +- qWarning("Could not create collator: %d", status); ++ qWarning() << "Could not create collator for" << name << ":: ICU return value:" << status; + collator = nullptr; + dirty = false; + return;