File indexing completed on 2025-03-16 04:01:02

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-01-20
0007  * Description : core image editor GUI implementation
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
0011  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "editorwindow_p.h"
0018 
0019 namespace Digikam
0020 {
0021 
0022 EditorWindow::EditorWindow(const QString& name, QWidget* const parent)
0023     : DXmlGuiWindow(parent),
0024       d            (new Private)
0025 {
0026     setConfigGroupName(QLatin1String("ImageViewer Settings"));
0027     setObjectName(name);
0028     setWindowFlags(Qt::Window);
0029     setFullScreenOptions(FS_EDITOR);
0030 
0031     m_nonDestructive               = true;
0032     m_contextMenu                  = nullptr;
0033     m_servicesMenu                 = nullptr;
0034     m_serviceAction                = nullptr;
0035     m_canvas                       = nullptr;
0036     m_openVersionAction            = nullptr;
0037     m_saveAction                   = nullptr;
0038     m_saveAsAction                 = nullptr;
0039     m_saveCurrentVersionAction     = nullptr;
0040     m_saveNewVersionAction         = nullptr;
0041     m_saveNewVersionAsAction       = nullptr;
0042     m_saveNewVersionInFormatAction = nullptr;
0043     m_resLabel                     = nullptr;
0044     m_nameLabel                    = nullptr;
0045     m_exportAction                 = nullptr;
0046     m_revertAction                 = nullptr;
0047     m_discardChangesAction         = nullptr;
0048     m_fileDeleteAction             = nullptr;
0049     m_forwardAction                = nullptr;
0050     m_backwardAction               = nullptr;
0051     m_firstAction                  = nullptr;
0052     m_lastAction                   = nullptr;
0053     m_applyToolAction              = nullptr;
0054     m_closeToolAction              = nullptr;
0055     m_undoAction                   = nullptr;
0056     m_redoAction                   = nullptr;
0057     m_showBarAction                = nullptr;
0058     m_splitter                     = nullptr;
0059     m_stackView                    = nullptr;
0060     m_setExifOrientationTag        = true;
0061     m_editingOriginalImage         = true;
0062     m_actionEnabledState           = false;
0063 
0064     // Settings containers instance.
0065 
0066     d->exposureSettings            = new ExposureSettingsContainer();
0067     d->toolIface                   = new EditorToolIface(this);
0068     m_IOFileSettings               = new IOFileSettings();
0069     //d->waitingLoop                 = new QEventLoop(this);
0070 }
0071 
0072 EditorWindow::~EditorWindow()
0073 {
0074     delete m_canvas;
0075     delete m_IOFileSettings;
0076     delete d->toolIface;
0077     delete d->exposureSettings;
0078     delete d;
0079 }
0080 
0081 SidebarSplitter* EditorWindow::sidebarSplitter() const
0082 {
0083     return m_splitter;
0084 }
0085 
0086 EditorStackView* EditorWindow::editorStackView() const
0087 {
0088     return m_stackView;
0089 }
0090 
0091 ExposureSettingsContainer* EditorWindow::exposureSettings() const
0092 {
0093     return d->exposureSettings;
0094 }
0095 
0096 void EditorWindow::setupContextMenu()
0097 {
0098     m_contextMenu = new QMenu(this);
0099 
0100     addAction2ContextMenu(QLatin1String("editorwindow_fullscreen"),            true);
0101     addAction2ContextMenu(QLatin1String("options_show_menubar"),               true);
0102     m_contextMenu->addSeparator();
0103 
0104     // --------------------------------------------------------
0105 
0106     addAction2ContextMenu(QLatin1String("editorwindow_backward"),              true);
0107     addAction2ContextMenu(QLatin1String("editorwindow_forward"),               true);
0108     m_contextMenu->addSeparator();
0109 
0110     // --------------------------------------------------------
0111 
0112     addAction2ContextMenu(QLatin1String("slideshow_plugin"),                   true);
0113     addAction2ContextMenu(QLatin1String("editorwindow_transform_rotateleft"),  true);
0114     addAction2ContextMenu(QLatin1String("editorwindow_transform_rotateright"), true);
0115     addAction2ContextMenu(QLatin1String("editorwindow_transform_crop"),        true);
0116     m_contextMenu->addSeparator();
0117 
0118     // --------------------------------------------------------
0119 
0120     addAction2ContextMenu(QLatin1String("editorwindow_delete"),                true);
0121 }
0122 
0123 void EditorWindow::setupStandardConnections()
0124 {
0125     connect(m_stackView, SIGNAL(signalToggleOffFitToWindow()),
0126             this, SLOT(slotToggleOffFitToWindow()));
0127 
0128     // -- Canvas connections ------------------------------------------------
0129 
0130     connect(m_canvas, SIGNAL(signalShowNextImage()),
0131             this, SLOT(slotForward()));
0132 
0133     connect(m_canvas, SIGNAL(signalShowPrevImage()),
0134             this, SLOT(slotBackward()));
0135 
0136     connect(m_canvas, SIGNAL(signalRightButtonClicked()),
0137             this, SLOT(slotContextMenu()));
0138 
0139     connect(m_stackView, SIGNAL(signalZoomChanged(bool,bool,double)),
0140             this, SLOT(slotZoomChanged(bool,bool,double)));
0141 
0142     connect(m_canvas, SIGNAL(signalChanged()),
0143             this, SLOT(slotChanged()));
0144 
0145     connect(m_canvas, SIGNAL(signalAddedDropedItems(QDropEvent*)),
0146             this, SLOT(slotAddedDropedItems(QDropEvent*)));
0147 
0148     connect(m_canvas->interface(), SIGNAL(signalUndoStateChanged()),
0149             this, SLOT(slotUndoStateChanged()));
0150 
0151     connect(m_canvas, SIGNAL(signalSelected(bool)),
0152             this, SLOT(slotSelected(bool)));
0153 
0154     connect(m_canvas, SIGNAL(signalPrepareToLoad()),
0155             this, SLOT(slotPrepareToLoad()));
0156 
0157     connect(m_canvas, SIGNAL(signalLoadingStarted(QString)),
0158             this, SLOT(slotLoadingStarted(QString)));
0159 
0160     connect(m_canvas, SIGNAL(signalLoadingFinished(QString,bool)),
0161             this, SLOT(slotLoadingFinished(QString,bool)));
0162 
0163     connect(m_canvas, SIGNAL(signalLoadingProgress(QString,float)),
0164             this, SLOT(slotLoadingProgress(QString,float)));
0165 
0166     connect(m_canvas, SIGNAL(signalSavingStarted(QString)),
0167             this, SLOT(slotSavingStarted(QString)));
0168 
0169     connect(m_canvas, SIGNAL(signalSavingFinished(QString,bool)),
0170             this, SLOT(slotSavingFinished(QString,bool)));
0171 
0172     connect(m_canvas, SIGNAL(signalSavingProgress(QString,float)),
0173             this, SLOT(slotSavingProgress(QString,float)));
0174 
0175     connect(m_canvas, SIGNAL(signalSelectionChanged(QRect)),
0176             this, SLOT(slotSelectionChanged(QRect)));
0177 
0178     connect(m_canvas, SIGNAL(signalSelectionSetText(QRect)),
0179             this, SLOT(slotSelectionSetText(QRect)));
0180 
0181     connect(m_canvas->interface(), SIGNAL(signalFileOriginChanged(QString)),
0182             this, SLOT(slotFileOriginChanged(QString)));
0183 
0184     // -- status bar connections --------------------------------------
0185 
0186     connect(m_nameLabel, SIGNAL(signalCancelButtonPressed()),
0187             this, SLOT(slotNameLabelCancelButtonPressed()));
0188 
0189     connect(m_nameLabel, SIGNAL(signalCancelButtonPressed()),
0190             d->toolIface, SLOT(slotToolAborted()));
0191 
0192     // -- Icc settings connections --------------------------------------
0193 
0194     connect(IccSettings::instance(), SIGNAL(signalSettingsChanged()),
0195             this, SLOT(slotColorManagementOptionsChanged()));
0196 }
0197 
0198 void EditorWindow::setupStandardActions()
0199 {
0200     // -- Standard 'File' menu actions ---------------------------------------------
0201 
0202     KActionCollection* const ac = actionCollection();
0203 
0204     m_backwardAction = buildStdAction(StdBackAction, this, SLOT(slotBackward()), this);
0205     ac->addAction(QLatin1String("editorwindow_backward"), m_backwardAction);
0206     ac->setDefaultShortcuts(m_backwardAction, QList<QKeySequence>() << Qt::Key_PageUp << Qt::Key_Backspace
0207                                                                     << Qt::Key_Up << Qt::Key_Left);
0208     m_backwardAction->setEnabled(false);
0209 
0210     m_forwardAction = buildStdAction(StdForwardAction, this, SLOT(slotForward()), this);
0211     ac->addAction(QLatin1String("editorwindow_forward"), m_forwardAction);
0212     ac->setDefaultShortcuts(m_forwardAction, QList<QKeySequence>() << Qt::Key_PageDown << Qt::Key_Space
0213                                                                    << Qt::Key_Down << Qt::Key_Right);
0214     m_forwardAction->setEnabled(false);
0215 
0216     m_firstAction = new QAction(QIcon::fromTheme(QLatin1String("go-first")),
0217                                 i18nc("@action; go to first item", "&First"), this);
0218     connect(m_firstAction, SIGNAL(triggered()), this, SLOT(slotFirst()));
0219     ac->addAction(QLatin1String("editorwindow_first"), m_firstAction);
0220     ac->setDefaultShortcut(m_firstAction, QKeySequence(Qt::CTRL | Qt::Key_Home));
0221     m_firstAction->setEnabled(false);
0222 
0223     m_lastAction = new QAction(QIcon::fromTheme(QLatin1String("go-last")),
0224                                i18nc("@action; go to last item", "&Last"), this);
0225     connect(m_lastAction, SIGNAL(triggered()), this, SLOT(slotLast()));
0226     ac->addAction(QLatin1String("editorwindow_last"), m_lastAction);
0227     ac->setDefaultShortcut(m_lastAction, QKeySequence(Qt::CTRL | Qt::Key_End));
0228     m_lastAction->setEnabled(false);
0229 
0230     m_openVersionAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")),
0231                                       i18nc("@action", "Open Original"), this);
0232     connect(m_openVersionAction, SIGNAL(triggered()), this, SLOT(slotOpenOriginal()));
0233     ac->addAction(QLatin1String("editorwindow_openversion"), m_openVersionAction);
0234     ac->setDefaultShortcut(m_openVersionAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_End));
0235 
0236     m_saveAction = buildStdAction(StdSaveAction, this, SLOT(saveOrSaveAs()), this);
0237     ac->addAction(QLatin1String("editorwindow_save"), m_saveAction);
0238 
0239     m_saveAsAction = buildStdAction(StdSaveAsAction, this, SLOT(saveAs()), this);
0240     ac->addAction(QLatin1String("editorwindow_saveas"), m_saveAsAction);
0241 
0242     m_saveCurrentVersionAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")),
0243                                              i18nc("@action Save changes to current version", "Save Changes"), this);
0244     m_saveCurrentVersionAction->setToolTip(i18nc("@info:tooltip", "Save the modifications to the current version of the file"));
0245     connect(m_saveCurrentVersionAction, SIGNAL(triggered()), this, SLOT(saveCurrentVersion()));
0246     ac->addAction(QLatin1String("editorwindow_savecurrentversion"), m_saveCurrentVersionAction);
0247 
0248     m_saveNewVersionAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("list-add")),
0249                                                      i18nc("@action Save changes to a newly created version", "Save As New Version"), this);
0250     m_saveNewVersionAction->setToolTip(i18nc("@info:tooltip", "Save the current modifications to a new version of the file"));
0251     connect(m_saveNewVersionAction, SIGNAL(triggered()), this, SLOT(saveNewVersion()));
0252     ac->addAction(QLatin1String("editorwindow_savenewversion"), m_saveNewVersionAction);
0253 
0254     QAction* const m_saveNewVersionAsAction = new QAction(QIcon::fromTheme(QLatin1String("document-save-as")),
0255                                                     i18nc("@action Save changes to a newly created version, specifying the filename and format",
0256                                                           "Save New Version As..."), this);
0257     m_saveNewVersionAsAction->setToolTip(i18nc("@info:tooltip", "Save the current modifications to a new version of the file, "
0258                                                "specifying the filename and format"));
0259     connect(m_saveNewVersionAsAction, SIGNAL(triggered()), this, SLOT(saveNewVersionAs()));
0260 
0261     m_saveNewVersionInFormatAction = new QMenu(i18nc("@action Save As New Version...Save in format...",
0262                                                      "Save in Format"), this);
0263     m_saveNewVersionInFormatAction->setIcon(QIcon::fromTheme(QLatin1String("view-preview")));
0264     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "JPEG"),       QLatin1String("JPG"));
0265     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "TIFF"),       QLatin1String("TIFF"));
0266     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "PNG"),        QLatin1String("PNG"));
0267     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "PGF"),        QLatin1String("PGF"));
0268 
0269 #ifdef HAVE_JASPER
0270 
0271     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "JPEG 2000"),  QLatin1String("JP2"));
0272 
0273 #endif // HAVE_JASPER
0274 
0275 #ifdef HAVE_X265
0276 
0277     d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "HEIF"),       QLatin1String("HEIF"));
0278 
0279 #endif // HAVE_X265
0280 
0281     if (DPluginLoader::instance()->canExport(QLatin1String("JXL")))
0282     {
0283         d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "JXL"),    QLatin1String("JXL"));
0284     }
0285 
0286     if (DPluginLoader::instance()->canExport(QLatin1String("WEBP")))
0287     {
0288         d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "WEBP"),   QLatin1String("WEBP"));
0289     }
0290 
0291     if (DPluginLoader::instance()->canExport(QLatin1String("AVIF")))
0292     {
0293         d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "AVIF"),   QLatin1String("AVIF"));
0294     }
0295 
0296 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0297 
0298     m_saveNewVersionAction->popupMenu()->addAction(m_saveNewVersionAsAction);
0299     m_saveNewVersionAction->popupMenu()->addAction(m_saveNewVersionInFormatAction->menuAction());
0300 
0301 #else
0302 
0303     m_saveNewVersionAction->menu()->addAction(m_saveNewVersionAsAction);
0304     m_saveNewVersionAction->menu()->addAction(m_saveNewVersionInFormatAction->menuAction());
0305 
0306 #endif
0307 
0308     // This also triggers saveAs, but in the context of non-destructive we want a slightly different appearance
0309 
0310     m_exportAction = new QAction(QIcon::fromTheme(QLatin1String("document-export")),
0311                                  i18nc("@action", "Export"), this);
0312     m_exportAction->setToolTip(i18nc("@info:tooltip", "Save the file in a folder outside your collection"));
0313     connect(m_exportAction, SIGNAL(triggered()), this, SLOT(saveAs()));
0314     ac->addAction(QLatin1String("editorwindow_export"), m_exportAction);
0315     ac->setDefaultShortcut(m_exportAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_E)); // NOTE: Gimp shortcut
0316 
0317     m_revertAction = buildStdAction(StdRevertAction, this, SLOT(slotRevert()), this);
0318     ac->addAction(QLatin1String("editorwindow_revert"), m_revertAction);
0319 
0320     m_discardChangesAction = new QAction(QIcon::fromTheme(QLatin1String("task-reject")),
0321                                          i18nc("@action", "Discard Changes"), this);
0322     m_discardChangesAction->setToolTip(i18nc("@info:tooltip", "Discard all current changes to this file"));
0323     connect(m_discardChangesAction, SIGNAL(triggered()), this, SLOT(slotDiscardChanges()));
0324     ac->addAction(QLatin1String("editorwindow_discardchanges"), m_discardChangesAction);
0325 
0326     m_openVersionAction->setEnabled(false);
0327     m_saveAction->setEnabled(false);
0328     m_saveAsAction->setEnabled(false);
0329     m_saveCurrentVersionAction->setEnabled(false);
0330     m_saveNewVersionAction->setEnabled(false);
0331     m_revertAction->setEnabled(false);
0332     m_discardChangesAction->setEnabled(false);
0333 
0334     d->openWithAction = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-filetype-association")),
0335                                     i18nc("@action", "Open With Default Application"), this);
0336     d->openWithAction->setWhatsThis(i18nc("@info", "Open the item with default assigned application."));
0337     connect(d->openWithAction, SIGNAL(triggered()), this, SLOT(slotFileWithDefaultApplication()));
0338     ac->addAction(QLatin1String("open_with_default_application"), d->openWithAction);
0339     ac->setDefaultShortcut(d->openWithAction, QKeySequence(Qt::CTRL | Qt::Key_F4));
0340     d->openWithAction->setEnabled(false);
0341 
0342     m_fileDeleteAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")),
0343                                      i18nc("@action: Non-pluralized", "Move to Trash"), this);
0344     connect(m_fileDeleteAction, SIGNAL(triggered()), this, SLOT(slotDeleteCurrentItem()));
0345     ac->addAction(QLatin1String("editorwindow_delete"), m_fileDeleteAction);
0346     ac->setDefaultShortcut(m_fileDeleteAction, QKeySequence(Qt::Key_Delete));
0347     m_fileDeleteAction->setEnabled(false);
0348 
0349     QAction* const closeAction = buildStdAction(StdCloseAction, this, SLOT(slotClose()), this);
0350     ac->addAction(QLatin1String("editorwindow_close"), closeAction);
0351 
0352     // -- Standard 'Edit' menu actions ---------------------------------------------
0353 
0354     d->copyAction = buildStdAction(StdCopyAction, m_canvas, SLOT(slotCopy()), this);
0355     ac->addAction(QLatin1String("editorwindow_copy"), d->copyAction);
0356     d->copyAction->setEnabled(false);
0357 
0358     m_undoAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("edit-undo")),
0359                                            i18nc("@action", "Undo"), this);
0360     m_undoAction->setEnabled(false);
0361     ac->addAction(QLatin1String("editorwindow_undo"), m_undoAction);
0362     ac->setDefaultShortcut(m_undoAction, QKeySequence(Qt::CTRL | Qt::Key_Z));
0363 
0364 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0365 
0366     connect(m_undoAction->popupMenu(), SIGNAL(aboutToShow()),
0367             this, SLOT(slotAboutToShowUndoMenu()));
0368 
0369 #else
0370 
0371     connect(m_undoAction->menu(), SIGNAL(aboutToShow()),
0372             this, SLOT(slotAboutToShowUndoMenu()));
0373 
0374 #endif
0375 
0376     // connect simple undo action
0377 
0378     connect(m_undoAction, &QAction::triggered,
0379             this, [this]()
0380         {
0381              m_canvas->slotUndo(1);
0382         }
0383     );
0384 
0385     m_redoAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("edit-redo")),
0386                                            i18nc("@action", "Redo"), this);
0387     m_redoAction->setEnabled(false);
0388     ac->addAction(QLatin1String("editorwindow_redo"), m_redoAction);
0389     ac->setDefaultShortcut(m_redoAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Z));
0390 
0391 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0392 
0393     connect(m_redoAction->popupMenu(), SIGNAL(aboutToShow()),
0394             this, SLOT(slotAboutToShowRedoMenu()));
0395 
0396 #else
0397 
0398     connect(m_redoAction->menu(), SIGNAL(aboutToShow()),
0399             this, SLOT(slotAboutToShowRedoMenu()));
0400 
0401 #endif
0402 
0403     // connect simple redo action
0404 
0405     connect(m_redoAction, &QAction::triggered,
0406             this, [this]()
0407         {
0408             m_canvas->slotRedo(1);
0409         }
0410     );
0411 
0412     d->selectAllAction = new QAction(i18nc("@action: create a selection containing the full image", "Select All"), this);
0413     connect(d->selectAllAction, SIGNAL(triggered()), m_canvas, SLOT(slotSelectAll()));
0414     ac->addAction(QLatin1String("editorwindow_selectAll"), d->selectAllAction);
0415     ac->setDefaultShortcut(d->selectAllAction, QKeySequence(Qt::CTRL | Qt::Key_A));
0416 
0417     d->selectNoneAction = new QAction(i18nc("@action", "Select None"), this);
0418     connect(d->selectNoneAction, SIGNAL(triggered()), m_canvas, SLOT(slotSelectNone()));
0419     ac->addAction(QLatin1String("editorwindow_selectNone"), d->selectNoneAction);
0420     ac->setDefaultShortcut(d->selectNoneAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_A));
0421 
0422     // -- Standard 'View' menu actions ---------------------------------------------
0423 
0424     d->zoomPlusAction  = buildStdAction(StdZoomInAction, this, SLOT(slotIncreaseZoom()), this);
0425     ac->addAction(QLatin1String("editorwindow_zoomplus"), d->zoomPlusAction);
0426 
0427     d->zoomMinusAction = buildStdAction(StdZoomOutAction, this, SLOT(slotDecreaseZoom()), this);
0428     ac->addAction(QLatin1String("editorwindow_zoomminus"), d->zoomMinusAction);
0429 
0430     d->zoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")),
0431                                        i18nc("@action", "Zoom to 100%"), this);
0432     connect(d->zoomTo100percents, SIGNAL(triggered()), this, SLOT(slotZoomTo100Percents()));
0433     ac->addAction(QLatin1String("editorwindow_zoomto100percents"), d->zoomTo100percents);
0434     ac->setDefaultShortcut(d->zoomTo100percents, QKeySequence(Qt::CTRL | Qt::Key_Period));
0435 
0436     d->zoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")),
0437                                            i18nc("@action", "Fit to &Window"), this);
0438     d->zoomFitToWindowAction->setCheckable(true);
0439     connect(d->zoomFitToWindowAction, SIGNAL(triggered()), this, SLOT(slotToggleFitToWindow()));
0440     ac->addAction(QLatin1String("editorwindow_zoomfit2window"), d->zoomFitToWindowAction);
0441     ac->setDefaultShortcut(d->zoomFitToWindowAction, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_E));
0442 
0443     d->zoomFitToSelectAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-select-fit")),
0444                                            i18nc("@action", "Fit to &Selection"), this);
0445     connect(d->zoomFitToSelectAction, SIGNAL(triggered()), this, SLOT(slotFitToSelect()));
0446     ac->addAction(QLatin1String("editorwindow_zoomfit2select"), d->zoomFitToSelectAction);
0447     ac->setDefaultShortcut(d->zoomFitToSelectAction, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_S));   // NOTE: Photoshop 7 use ALT+CTRL+0
0448     d->zoomFitToSelectAction->setEnabled(false);
0449     d->zoomFitToSelectAction->setWhatsThis(i18nc("@info", "This option can be used to zoom the image to the "
0450                                                           "current selection area."));
0451 
0452     // **********************************************************
0453 
0454     QList<DPluginAction*> gactions = DPluginLoader::instance()->pluginsActions(DPluginAction::Generic, this);
0455 
0456     Q_FOREACH (DPluginAction* const gac, gactions)
0457     {
0458         gac->setEnabled(false);
0459     }
0460 
0461     QList<DPluginAction*> eactions = DPluginLoader::instance()->pluginsActions(DPluginAction::Editor, this);
0462 
0463     Q_FOREACH (DPluginAction* const eac, eactions)
0464     {
0465         eac->setEnabled(false);
0466     }
0467 
0468     // --------------------------------------------------------
0469 
0470     createFullScreenAction(QLatin1String("editorwindow_fullscreen"));
0471     createSidebarActions();
0472 
0473     d->viewUnderExpoAction = new QAction(QIcon::fromTheme(QLatin1String("underexposure")),
0474                                          i18nc("@action", "Under-Exposure Indicator"), this);
0475     d->viewUnderExpoAction->setCheckable(true);
0476     d->viewUnderExpoAction->setWhatsThis(i18nc("@info", "Set this option to display black "
0477                                                "overlaid on the image. This will help you to avoid "
0478                                                "under-exposing the image."));
0479     connect(d->viewUnderExpoAction, SIGNAL(triggered(bool)), this, SLOT(slotSetUnderExposureIndicator(bool)));
0480     ac->addAction(QLatin1String("editorwindow_underexposure"), d->viewUnderExpoAction);
0481     ac->setDefaultShortcut(d->viewUnderExpoAction, QKeySequence(Qt::Key_F11));
0482 
0483     d->viewOverExpoAction = new QAction(QIcon::fromTheme(QLatin1String("overexposure")),
0484                                         i18nc("@action", "Over-Exposure Indicator"), this);
0485     d->viewOverExpoAction->setCheckable(true);
0486     d->viewOverExpoAction->setWhatsThis(i18nc("@info", "Set this option to display white "
0487                                               "overlaid on the image. This will help you to avoid "
0488                                               "over-exposing the image."));
0489     connect(d->viewOverExpoAction, SIGNAL(triggered(bool)), this, SLOT(slotSetOverExposureIndicator(bool)));
0490     ac->addAction(QLatin1String("editorwindow_overexposure"), d->viewOverExpoAction);
0491     ac->setDefaultShortcut(d->viewOverExpoAction, QKeySequence(Qt::CTRL | Qt::Key_F11));
0492 
0493     d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")),
0494                                       i18nc("@action", "Color-Managed View"), this);
0495     d->viewCMViewAction->setCheckable(true);
0496     connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView()));
0497     ac->addAction(QLatin1String("editorwindow_cmview"), d->viewCMViewAction);
0498     ac->setDefaultShortcut(d->viewCMViewAction, QKeySequence(Qt::Key_F12));
0499 
0500     d->softProofOptionsAction = new QAction(QIcon::fromTheme(QLatin1String("document-print")),
0501                                             i18nc("@action", "Soft Proofing Options..."), this);
0502     connect(d->softProofOptionsAction, SIGNAL(triggered()), this, SLOT(slotSoftProofingOptions()));
0503     ac->addAction(QLatin1String("editorwindow_softproofoptions"), d->softProofOptionsAction);
0504 
0505     d->viewSoftProofAction = new QAction(QIcon::fromTheme(QLatin1String("document-preview-archive")),
0506                                          i18nc("@action", "Soft Proofing View"), this);
0507     d->viewSoftProofAction->setCheckable(true);
0508     connect(d->viewSoftProofAction, SIGNAL(triggered()), this, SLOT(slotUpdateSoftProofingState()));
0509     ac->addAction(QLatin1String("editorwindow_softproofview"), d->viewSoftProofAction);
0510 
0511     // -- Standard 'Transform' menu actions ---------------------------------------------
0512 
0513     d->cropAction = new QAction(QIcon::fromTheme(QLatin1String("transform-crop-and-resize")),
0514                                 i18nc("@action", "Crop to Selection"), this);
0515     connect(d->cropAction, SIGNAL(triggered()), m_canvas, SLOT(slotCrop()));
0516     d->cropAction->setEnabled(false);
0517     d->cropAction->setWhatsThis(i18nc("@info", "This option can be used to crop the image. "
0518                                       "Select a region of the image to enable this action."));
0519     ac->addAction(QLatin1String("editorwindow_transform_crop"), d->cropAction);
0520     ac->setDefaultShortcut(d->cropAction, QKeySequence(Qt::CTRL | Qt::Key_X));
0521 
0522     // -- Standard 'Flip' menu actions ---------------------------------------------
0523 
0524     d->flipHorizAction = new QAction(QIcon::fromTheme(QLatin1String("object-flip-horizontal")),
0525                                      i18nc("@action", "Flip Horizontally"), this);
0526     connect(d->flipHorizAction, SIGNAL(triggered()), m_canvas, SLOT(slotFlipHoriz()));
0527     connect(d->flipHorizAction, SIGNAL(triggered()), this, SLOT(slotFlipHIntoQue()));
0528     ac->addAction(QLatin1String("editorwindow_transform_fliphoriz"), d->flipHorizAction);
0529     ac->setDefaultShortcut(d->flipHorizAction, QKeySequence(Qt::CTRL | Qt::Key_Asterisk));
0530     d->flipHorizAction->setEnabled(false);
0531 
0532     d->flipVertAction = new QAction(QIcon::fromTheme(QLatin1String("object-flip-vertical")),
0533                                     i18nc("@action", "Flip Vertically"), this);
0534     connect(d->flipVertAction, SIGNAL(triggered()), m_canvas, SLOT(slotFlipVert()));
0535     connect(d->flipVertAction, SIGNAL(triggered()), this, SLOT(slotFlipVIntoQue()));
0536     ac->addAction(QLatin1String("editorwindow_transform_flipvert"), d->flipVertAction);
0537     ac->setDefaultShortcut(d->flipVertAction, QKeySequence(Qt::CTRL | Qt::Key_Slash));
0538     d->flipVertAction->setEnabled(false);
0539 
0540     // -- Standard 'Rotate' menu actions ----------------------------------------
0541 
0542     d->rotateLeftAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-left")),
0543                                       i18nc("@action", "Rotate Left"), this);
0544     connect(d->rotateLeftAction, SIGNAL(triggered()), m_canvas, SLOT(slotRotate270()));
0545     connect(d->rotateLeftAction, SIGNAL(triggered()), this, SLOT(slotRotateLeftIntoQue()));
0546     ac->addAction(QLatin1String("editorwindow_transform_rotateleft"), d->rotateLeftAction);
0547     ac->setDefaultShortcut(d->rotateLeftAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Left));
0548     d->rotateLeftAction->setEnabled(false);
0549 
0550     d->rotateRightAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-right")),
0551                                        i18nc("@action", "Rotate Right"), this);
0552     connect(d->rotateRightAction, SIGNAL(triggered()), m_canvas, SLOT(slotRotate90()));
0553     connect(d->rotateRightAction, SIGNAL(triggered()), this, SLOT(slotRotateRightIntoQue()));
0554     ac->addAction(QLatin1String("editorwindow_transform_rotateright"), d->rotateRightAction);
0555     ac->setDefaultShortcut(d->rotateRightAction, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Right));
0556     d->rotateRightAction->setEnabled(false);
0557 
0558     m_showBarAction = thumbBar()->getToggleAction(this);
0559     ac->addAction(QLatin1String("editorwindow_showthumbs"), m_showBarAction);
0560     ac->setDefaultShortcut(m_showBarAction, QKeySequence(Qt::CTRL | Qt::Key_T));
0561 
0562     // Provides a menu entry that allows showing/hiding the toolbar(s)
0563 
0564     setStandardToolBarMenuEnabled(true);
0565 
0566     // Provides a menu entry that allows showing/hiding the statusbar
0567 
0568     createStandardStatusBarAction();
0569 
0570     // Standard 'Configure' menu actions
0571 
0572     createSettingsActions();
0573 
0574     // ---------------------------------------------------------------------------------
0575 
0576     ThemeManager::instance()->registerThemeActions(this);
0577 
0578     connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()),
0579             this, SLOT(slotThemeChanged()));
0580 
0581     // -- Keyboard-only actions --------------------------------------------------------
0582 
0583     QAction* const altBackwardAction = new QAction(i18nc("@action", "Previous Image"), this);
0584     ac->addAction(QLatin1String("editorwindow_backward_shift_space"), altBackwardAction);
0585     ac->setDefaultShortcut(altBackwardAction, QKeySequence(Qt::SHIFT | Qt::Key_Space));
0586     connect(altBackwardAction, SIGNAL(triggered()), this, SLOT(slotBackward()));
0587 
0588     // -- Tool control actions ---------------------------------------------------------
0589 
0590     m_applyToolAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")),
0591                                     i18nc("@action", "OK"), this);
0592     ac->addAction(QLatin1String("editorwindow_applytool"), m_applyToolAction);
0593     ac->setDefaultShortcut(m_applyToolAction, QKeySequence(Qt::Key_Return));
0594     connect(m_applyToolAction, SIGNAL(triggered()), this, SLOT(slotApplyTool()));
0595 
0596     m_closeToolAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-cancel")),
0597                                     i18nc("@action", "Cancel"), this);
0598     ac->addAction(QLatin1String("editorwindow_closetool"), m_closeToolAction);
0599     ac->setDefaultShortcut(m_closeToolAction, QKeySequence(Qt::Key_Escape));
0600     connect(m_closeToolAction, SIGNAL(triggered()), this, SLOT(slotCloseTool()));
0601 
0602     toggleNonDestructiveActions();
0603     toggleToolActions();
0604 }
0605 
0606 void EditorWindow::setupStatusBar()
0607 {
0608     m_nameLabel  = new StatusProgressBar(statusBar());
0609     m_nameLabel->setAlignment(Qt::AlignCenter);
0610     statusBar()->addWidget(m_nameLabel, 100);
0611 
0612     d->infoLabel = new DAdjustableLabel(statusBar());
0613     d->infoLabel->setAdjustedText(i18nc("@label", "No selection"));
0614     d->infoLabel->setAlignment(Qt::AlignCenter);
0615     statusBar()->addWidget(d->infoLabel, 100);
0616     d->infoLabel->setToolTip(i18nc("@info", "Information about current image selection"));
0617 
0618     m_resLabel   = new DAdjustableLabel(statusBar());
0619     m_resLabel->setAlignment(Qt::AlignCenter);
0620     statusBar()->addWidget(m_resLabel, 100);
0621     m_resLabel->setToolTip(i18nc("@info", "Information about image size"));
0622 
0623     d->zoomBar   = new DZoomBar(statusBar());
0624     d->zoomBar->setZoomToFitAction(d->zoomFitToWindowAction);
0625     d->zoomBar->setZoomTo100Action(d->zoomTo100percents);
0626     d->zoomBar->setZoomPlusAction(d->zoomPlusAction);
0627     d->zoomBar->setZoomMinusAction(d->zoomMinusAction);
0628     d->zoomBar->setBarMode(DZoomBar::PreviewZoomCtrl);
0629     statusBar()->addPermanentWidget(d->zoomBar);
0630 
0631     connect(d->zoomBar, SIGNAL(signalZoomSliderChanged(int)),
0632             m_stackView, SLOT(slotZoomSliderChanged(int)));
0633 
0634     connect(d->zoomBar, SIGNAL(signalZoomValueEdited(double)),
0635             m_stackView, SLOT(setZoomFactor(double)));
0636 
0637     d->previewToolBar = new PreviewToolBar(statusBar());
0638     d->previewToolBar->registerMenuActionGroup(this);
0639     d->previewToolBar->setEnabled(false);
0640     statusBar()->addPermanentWidget(d->previewToolBar);
0641 
0642     connect(d->previewToolBar, SIGNAL(signalPreviewModeChanged(int)),
0643             this, SIGNAL(signalPreviewModeChanged(int)));
0644 
0645     QWidget* const buttonsBox      = new QWidget(statusBar());
0646     QHBoxLayout* const hlay        = new QHBoxLayout(buttonsBox);
0647     QButtonGroup* const buttonsGrp = new QButtonGroup(buttonsBox);
0648     buttonsGrp->setExclusive(false);
0649 
0650     d->underExposureIndicator = new QToolButton(buttonsBox);
0651     d->underExposureIndicator->setDefaultAction(d->viewUnderExpoAction);
0652     d->underExposureIndicator->setFocusPolicy(Qt::NoFocus);
0653 
0654     d->overExposureIndicator  = new QToolButton(buttonsBox);
0655     d->overExposureIndicator->setDefaultAction(d->viewOverExpoAction);
0656     d->overExposureIndicator->setFocusPolicy(Qt::NoFocus);
0657 
0658     d->cmViewIndicator        = new QToolButton(buttonsBox);
0659     d->cmViewIndicator->setDefaultAction(d->viewCMViewAction);
0660     d->cmViewIndicator->setFocusPolicy(Qt::NoFocus);
0661 
0662     buttonsGrp->addButton(d->underExposureIndicator);
0663     buttonsGrp->addButton(d->overExposureIndicator);
0664     buttonsGrp->addButton(d->cmViewIndicator);
0665 
0666     hlay->setSpacing(0);
0667     hlay->setContentsMargins(QMargins());
0668     hlay->addWidget(d->underExposureIndicator);
0669     hlay->addWidget(d->overExposureIndicator);
0670     hlay->addWidget(d->cmViewIndicator);
0671 
0672     statusBar()->addPermanentWidget(buttonsBox);
0673 }
0674 
0675 void EditorWindow::slotAboutToShowUndoMenu()
0676 {
0677 
0678 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0679 
0680     m_undoAction->popupMenu()->clear();
0681 
0682 #else
0683 
0684     m_undoAction->menu()->clear();
0685 
0686 #endif
0687 
0688     QStringList titles = m_canvas->interface()->getUndoHistory();
0689 
0690     for (int i = 0 ; i < titles.size() ; ++i)
0691     {
0692 
0693 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0694 
0695         QAction* const action = m_undoAction->popupMenu()->addAction(titles.at(i));
0696 
0697 #else
0698 
0699         QAction* const action = m_undoAction->menu()->addAction(titles.at(i));
0700 
0701 #endif
0702 
0703         int id                = i + 1;
0704 
0705         connect(action, &QAction::triggered,
0706                 this, [this, id]()
0707             {
0708                 m_canvas->slotUndo(id);
0709             }
0710         );
0711     }
0712 }
0713 
0714 void EditorWindow::slotAboutToShowRedoMenu()
0715 {
0716 
0717 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0718 
0719     m_redoAction->popupMenu()->clear();
0720 
0721 #else
0722 
0723     m_redoAction->menu()->clear();
0724 
0725 #endif
0726 
0727     QStringList titles = m_canvas->interface()->getRedoHistory();
0728 
0729     for (int i = 0 ; i < titles.size() ; ++i)
0730     {
0731 
0732 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0733 
0734         QAction* const action = m_redoAction->popupMenu()->addAction(titles.at(i));
0735 
0736 #else
0737 
0738         QAction* const action = m_redoAction->menu()->addAction(titles.at(i));
0739 
0740 #endif
0741 
0742         int id                = i + 1;
0743 
0744         connect(action, &QAction::triggered,
0745                 this, [this, id]()
0746             {
0747                 m_canvas->slotRedo(id);
0748             }
0749         );
0750     }
0751 }
0752 
0753 void EditorWindow::slotIncreaseZoom()
0754 {
0755     m_stackView->increaseZoom();
0756 }
0757 
0758 void EditorWindow::slotDecreaseZoom()
0759 {
0760     m_stackView->decreaseZoom();
0761 }
0762 
0763 void EditorWindow::slotToggleFitToWindow()
0764 {
0765     d->zoomPlusAction->setEnabled(true);
0766     d->zoomBar->setEnabled(true);
0767     d->zoomMinusAction->setEnabled(true);
0768     m_stackView->toggleFitToWindow();
0769 }
0770 
0771 void EditorWindow::slotFitToSelect()
0772 {
0773     d->zoomPlusAction->setEnabled(true);
0774     d->zoomBar->setEnabled(true);
0775     d->zoomMinusAction->setEnabled(true);
0776     m_stackView->fitToSelect();
0777 }
0778 
0779 void EditorWindow::slotZoomTo100Percents()
0780 {
0781     d->zoomPlusAction->setEnabled(true);
0782     d->zoomBar->setEnabled(true);
0783     d->zoomMinusAction->setEnabled(true);
0784     m_stackView->zoomTo100Percent();
0785 }
0786 
0787 void EditorWindow::slotZoomChanged(bool isMax, bool isMin, double zoom)
0788 {
0789 /*
0790     qCDebug(DIGIKAM_GENERAL_LOG) << "EditorWindow::slotZoomChanged";
0791 */
0792     d->zoomPlusAction->setEnabled(!isMax);
0793     d->zoomMinusAction->setEnabled(!isMin);
0794 
0795     double zmin = m_stackView->zoomMin();
0796     double zmax = m_stackView->zoomMax();
0797     d->zoomBar->setZoom(zoom, zmin, zmax);
0798 }
0799 
0800 void EditorWindow::slotToggleOffFitToWindow()
0801 {
0802     d->zoomFitToWindowAction->blockSignals(true);
0803     d->zoomFitToWindowAction->setChecked(false);
0804     d->zoomFitToWindowAction->blockSignals(false);
0805 }
0806 
0807 void EditorWindow::readStandardSettings()
0808 {
0809     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0810     KConfigGroup group        = config->group(configGroupName());
0811 
0812     // Restore full screen Mode
0813 
0814     readFullScreenSettings(group);
0815 
0816     // Restore Auto zoom action
0817 
0818     bool autoZoom = group.readEntry(d->configAutoZoomEntry, true);
0819 
0820     if (autoZoom)
0821     {
0822         d->zoomFitToWindowAction->trigger();
0823     }
0824 
0825     slotSetUnderExposureIndicator(group.readEntry(d->configUnderExposureIndicatorEntry, false));
0826     slotSetOverExposureIndicator(group.readEntry(d->configOverExposureIndicatorEntry, false));
0827     d->previewToolBar->readSettings(group);
0828 }
0829 
0830 void EditorWindow::applyStandardSettings()
0831 {
0832     applyColorManagementSettings();
0833     d->toolIface->updateICCSettings();
0834 
0835     applyIOSettings();
0836 
0837     // -- GUI Settings -------------------------------------------------------
0838 
0839     KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
0840 
0841     d->legacyUpdateSplitterState(group);
0842     m_splitter->restoreState(group);
0843     readFullScreenSettings(group);
0844 
0845     slotThemeChanged();
0846 
0847     // -- Exposure Indicators Settings ---------------------------------------
0848 
0849     d->exposureSettings->underExposureColor    = group.readEntry(d->configUnderExposureColorEntry,    QColor(Qt::white));
0850     d->exposureSettings->underExposurePercent  = group.readEntry(d->configUnderExposurePercentsEntry, 1.0);
0851     d->exposureSettings->overExposureColor     = group.readEntry(d->configOverExposureColorEntry,     QColor(Qt::black));
0852     d->exposureSettings->overExposurePercent   = group.readEntry(d->configOverExposurePercentsEntry,  1.0);
0853     d->exposureSettings->exposureIndicatorMode = group.readEntry(d->configExpoIndicatorModeEntry,     true);
0854     d->toolIface->updateExposureSettings();
0855 
0856     // -- Metadata Settings --------------------------------------------------
0857 
0858     MetaEngineSettingsContainer writeSettings = MetaEngineSettings::instance()->settings();
0859     m_setExifOrientationTag                   = writeSettings.exifSetOrientation;
0860     m_canvas->setExifOrient(writeSettings.exifRotate);
0861 }
0862 
0863 void EditorWindow::applyIOSettings()
0864 {
0865     // -- JPEG, PNG, TIFF, JPEG2000, PGF files format settings ----------------
0866 
0867     KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
0868 
0869     m_IOFileSettings->JPEGCompression     = DImgLoader::convertCompressionForLibJpeg(group.readEntry(d->configJpegCompressionEntry, 75));
0870 
0871     // Medium subsampling
0872 
0873     m_IOFileSettings->JPEGSubSampling     = group.readEntry(d->configJpegSubSamplingEntry, 1);
0874 
0875     m_IOFileSettings->PNGCompression      = DImgLoader::convertCompressionForLibPng(group.readEntry(d->configPngCompressionEntry,    9));
0876 
0877     // TIFF compression setting.
0878 
0879     m_IOFileSettings->TIFFCompression     = group.readEntry(d->configTiffCompressionEntry,     false);
0880 
0881     // JPEG2000 quality slider settings : 1 - 100
0882 
0883     m_IOFileSettings->JPEG2000Compression = group.readEntry(d->configJpeg2000CompressionEntry, 75);
0884 
0885     // JPEG2000 LossLess setting.
0886 
0887     m_IOFileSettings->JPEG2000LossLess    = group.readEntry(d->configJpeg2000LossLessEntry,    true);
0888 
0889     // PGF quality slider settings : 1 - 9
0890 
0891     m_IOFileSettings->PGFCompression      = group.readEntry(d->configPgfCompressionEntry,      3);
0892 
0893     // PGF LossLess setting.
0894 
0895     m_IOFileSettings->PGFLossLess         = group.readEntry(d->configPgfLossLessEntry,         true);
0896 
0897     // HEIF quality slider settings : 1 - 100
0898 
0899     m_IOFileSettings->HEIFCompression     = group.readEntry(d->configHeifCompressionEntry,     75);
0900 
0901     // HEIF LossLess setting.
0902 
0903     m_IOFileSettings->HEIFLossLess        = group.readEntry(d->configHeifLossLessEntry,        true);
0904 
0905     // JXL quality slider settings : 1 - 99
0906 
0907     m_IOFileSettings->JXLCompression      = group.readEntry(d->configJxlCompressionEntry,      75);
0908 
0909     // JXL LossLess setting.
0910 
0911     m_IOFileSettings->JXLLossLess         = group.readEntry(d->configJxlLossLessEntry,         true);
0912 
0913     // WEBP quality slider settings : 1 - 99
0914 
0915     m_IOFileSettings->WEBPCompression     = group.readEntry(d->configWebpCompressionEntry,     75);
0916 
0917     // WEBP LossLess setting.
0918 
0919     m_IOFileSettings->WEBPLossLess         = group.readEntry(d->configWebpLossLessEntry,       true);
0920 
0921     // AVIF quality slider settings : 1 - 99
0922 
0923     m_IOFileSettings->AVIFCompression     = group.readEntry(d->configAvifCompressionEntry,     75);
0924 
0925     // AVIF LossLess setting.
0926 
0927     m_IOFileSettings->AVIFLossLess        = group.readEntry(d->configAvifLossLessEntry,        true);
0928 
0929     // -- RAW images decoding settings ------------------------------------------------------
0930 
0931     m_IOFileSettings->useRAWImport        = group.readEntry(d->configUseRawImportToolEntry, false);
0932     m_IOFileSettings->rawImportToolIid    = group.readEntry(d->configRawImportToolIidEntry, QString::fromLatin1("org.kde.digikam.plugin.rawimport.Native"));
0933     DRawDecoderWidget::readSettings(m_IOFileSettings->rawDecodingSettings.rawPrm, group);
0934 
0935     // Raw Color Management settings:
0936     // If digiKam Color Management is enabled, no need to correct color of decoded RAW image,
0937     // else, sRGB color workspace will be used.
0938 
0939     ICCSettingsContainer settings = IccSettings::instance()->settings();
0940 
0941     if (settings.enableCM)
0942     {
0943         if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors)
0944         {
0945             m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::CUSTOMOUTPUTCS;
0946             m_IOFileSettings->rawDecodingSettings.rawPrm.outputProfile    = settings.workspaceProfile;
0947         }
0948         else
0949         {
0950             m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::RAWCOLOR;
0951         }
0952     }
0953     else
0954     {
0955         m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::SRGB;
0956     }
0957 }
0958 
0959 void EditorWindow::applyColorManagementSettings()
0960 {
0961     ICCSettingsContainer settings = IccSettings::instance()->settings();
0962 
0963     d->toolIface->updateICCSettings();
0964     m_canvas->setICCSettings(settings);
0965 
0966     d->viewCMViewAction->blockSignals(true);
0967     d->viewCMViewAction->setEnabled(settings.enableCM);
0968     d->viewCMViewAction->setChecked(settings.useManagedView);
0969     setColorManagedViewIndicatorToolTip(settings.enableCM, settings.useManagedView);
0970     d->viewCMViewAction->blockSignals(false);
0971 
0972     d->viewSoftProofAction->setEnabled(settings.enableCM && !settings.defaultProofProfile.isEmpty());
0973     d->softProofOptionsAction->setEnabled(settings.enableCM);
0974 }
0975 
0976 void EditorWindow::saveStandardSettings()
0977 {
0978     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0979     KConfigGroup group        = config->group(configGroupName());
0980 
0981     group.writeEntry(d->configAutoZoomEntry, d->zoomFitToWindowAction->isChecked());
0982     m_splitter->saveState(group);
0983 
0984     group.writeEntry("Show Thumbbar", thumbBar()->shouldBeVisible());
0985     group.writeEntry(d->configUnderExposureIndicatorEntry, d->exposureSettings->underExposureIndicator);
0986     group.writeEntry(d->configOverExposureIndicatorEntry,  d->exposureSettings->overExposureIndicator);
0987     d->previewToolBar->writeSettings(group);
0988     config->sync();
0989 }
0990 
0991 /**
0992  * Method used by Editor Tools. Only tools based on imageregionwidget support zooming.
0993  * TODO: Fix this behavior when editor tool preview widgets will be factored.
0994  */
0995 void EditorWindow::toggleZoomActions(bool val)
0996 {
0997     d->zoomMinusAction->setEnabled(val);
0998     d->zoomPlusAction->setEnabled(val);
0999     d->zoomTo100percents->setEnabled(val);
1000     d->zoomFitToWindowAction->setEnabled(val);
1001     d->zoomBar->setEnabled(val);
1002 }
1003 
1004 void EditorWindow::readSettings()
1005 {
1006     readStandardSettings();
1007 }
1008 
1009 void EditorWindow::saveSettings()
1010 {
1011     saveStandardSettings();
1012 }
1013 
1014 void EditorWindow::toggleActions(bool val)
1015 {
1016     toggleStandardActions(val);
1017 }
1018 
1019 bool EditorWindow::actionEnabledState() const
1020 {
1021     return m_actionEnabledState;
1022 }
1023 
1024 void EditorWindow::toggleStandardActions(bool val)
1025 {
1026     d->zoomFitToSelectAction->setEnabled(val);
1027     toggleZoomActions(val);
1028 
1029     m_actionEnabledState = val;
1030 
1031     m_forwardAction->setEnabled(val);
1032     m_backwardAction->setEnabled(val);
1033     m_firstAction->setEnabled(val);
1034     m_lastAction->setEnabled(val);
1035     d->rotateLeftAction->setEnabled(val);
1036     d->rotateRightAction->setEnabled(val);
1037     d->flipHorizAction->setEnabled(val);
1038     d->flipVertAction->setEnabled(val);
1039     m_fileDeleteAction->setEnabled(val);
1040     m_saveAsAction->setEnabled(val);
1041     d->openWithAction->setEnabled(val);
1042     m_exportAction->setEnabled(val);
1043     d->selectAllAction->setEnabled(val);
1044     d->selectNoneAction->setEnabled(val);
1045 
1046     QList<DPluginAction*> actions = DPluginLoader::instance()->pluginsActions(DPluginAction::Generic, this);
1047 
1048     Q_FOREACH (DPluginAction* const ac, actions)
1049     {
1050         ac->setEnabled(val);
1051     }
1052 
1053     // these actions are special: They are turned off if val is false,
1054     // but if val is true, they may be turned on or off.
1055 
1056     if (val)
1057     {
1058         // Update actions by retrieving current values
1059 
1060         slotUndoStateChanged();
1061     }
1062     else
1063     {
1064         m_openVersionAction->setEnabled(false);
1065         m_revertAction->setEnabled(false);
1066         m_saveAction->setEnabled(false);
1067         m_saveCurrentVersionAction->setEnabled(false);
1068         m_saveNewVersionAction->setEnabled(false);
1069         m_discardChangesAction->setEnabled(false);
1070         m_undoAction->setEnabled(false);
1071         m_redoAction->setEnabled(false);
1072     }
1073 
1074     // Editor Tools actions
1075 
1076     QList<DPluginAction*> actions2 = DPluginLoader::instance()->pluginsActions(DPluginAction::Editor, this);
1077 
1078     Q_FOREACH (DPluginAction* const ac, actions2)
1079     {
1080         ac->setEnabled(val);
1081     }
1082 }
1083 
1084 void EditorWindow::toggleNonDestructiveActions()
1085 {
1086     m_saveAction->setVisible(!m_nonDestructive);
1087     m_saveAsAction->setVisible(!m_nonDestructive);
1088     m_revertAction->setVisible(!m_nonDestructive);
1089 
1090     m_openVersionAction->setVisible(m_nonDestructive);
1091     m_saveCurrentVersionAction->setVisible(m_nonDestructive);
1092     m_saveNewVersionAction->setVisible(m_nonDestructive);
1093     m_exportAction->setVisible(m_nonDestructive);
1094     m_discardChangesAction->setVisible(m_nonDestructive);
1095 }
1096 
1097 void EditorWindow::toggleToolActions(EditorTool* tool)
1098 {
1099     if (tool)
1100     {
1101         m_applyToolAction->setText(tool->toolSettings()->button(EditorToolSettings::Ok)->text());
1102         m_applyToolAction->setIcon(tool->toolSettings()->button(EditorToolSettings::Ok)->icon());
1103         m_applyToolAction->setToolTip(tool->toolSettings()->button(EditorToolSettings::Ok)->toolTip());
1104 
1105         m_closeToolAction->setText(tool->toolSettings()->button(EditorToolSettings::Cancel)->text());
1106         m_closeToolAction->setIcon(tool->toolSettings()->button(EditorToolSettings::Cancel)->icon());
1107         m_closeToolAction->setToolTip(tool->toolSettings()->button(EditorToolSettings::Cancel)->toolTip());
1108     }
1109 
1110     m_applyToolAction->setVisible(tool);
1111     m_closeToolAction->setVisible(tool);
1112 }
1113 
1114 void EditorWindow::slotLoadingProgress(const QString&, float progress)
1115 {
1116     m_nameLabel->setProgressValue((int)(progress * 100.0));
1117 }
1118 
1119 void EditorWindow::slotSavingProgress(const QString&, float progress)
1120 {
1121     m_nameLabel->setProgressValue((int)(progress * 100.0));
1122 
1123     if (m_savingProgressDialog)
1124     {
1125         m_savingProgressDialog->setValue((int)(progress * 100.0));
1126     }
1127 }
1128 
1129 void EditorWindow::execSavingProgressDialog()
1130 {
1131     if (m_savingProgressDialog)
1132     {
1133         return;
1134     }
1135 
1136     m_savingProgressDialog = new QProgressDialog(this);
1137     m_savingProgressDialog->setWindowTitle(i18nc("@title:window", "Saving Image..."));
1138     m_savingProgressDialog->setLabelText(i18nc("@label", "Please wait for the image to be saved..."));
1139     m_savingProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
1140     m_savingProgressDialog->setAutoClose(true);
1141     m_savingProgressDialog->setMinimumDuration(1000);
1142     m_savingProgressDialog->setMaximum(100);
1143 
1144     // we must enter a fully modal dialog, no QEventLoop is sufficient for KWin to accept longer waiting times
1145 
1146     m_savingProgressDialog->setModal(true);
1147     m_savingProgressDialog->exec();
1148 }
1149 
1150 bool EditorWindow::promptForOverWrite()
1151 {
1152 
1153     QUrl destination = saveDestinationUrl();
1154 
1155     if (destination.isLocalFile())
1156     {
1157 
1158         QFileInfo fi(m_canvas->currentImageFilePath());
1159         QString warnMsg(i18nc("@info", "About to overwrite file \"%1\"\nAre you sure?",
1160                               QDir::toNativeSeparators(fi.fileName())));
1161 
1162         return (DMessageBox::showContinueCancel(QMessageBox::Warning,
1163                                                 this,
1164                                                 i18nc("@title:window, warning while saving progress", "Warning"),
1165                                                 warnMsg,
1166                                                 QLatin1String("editorWindowSaveOverwrite"))
1167                 ==  QMessageBox::Yes);
1168 
1169     }
1170     else
1171     {
1172         // in this case it will handle the overwrite request
1173 
1174         return true;
1175     }
1176 }
1177 
1178 void EditorWindow::slotUndoStateChanged()
1179 {
1180     UndoState state = m_canvas->interface()->undoState();
1181 
1182     // RAW conversion qualifies as a "non-undoable" action
1183     // You can save as new version, but cannot undo or revert
1184 
1185     m_undoAction->setEnabled(state.hasUndo);
1186     m_redoAction->setEnabled(state.hasRedo);
1187     m_revertAction->setEnabled(state.hasUndoableChanges);
1188 
1189     m_saveAction->setEnabled(state.hasChanges);
1190     m_saveCurrentVersionAction->setEnabled(state.hasChanges);
1191     m_saveNewVersionAction->setEnabled(state.hasChanges);
1192     m_discardChangesAction->setEnabled(state.hasUndoableChanges);
1193 
1194     m_openVersionAction->setEnabled(hasOriginalToRestore());
1195 }
1196 
1197 bool EditorWindow::hasOriginalToRestore()
1198 {
1199     return m_canvas->interface()->getResolvedInitialHistory().hasOriginalReferredImage();
1200 }
1201 
1202 DImageHistory EditorWindow::resolvedImageHistory(const DImageHistory& history)
1203 {
1204     // simple, database-less version
1205 
1206     DImageHistory r = history;
1207     QList<DImageHistory::Entry>::iterator it;
1208 
1209     for (it = r.entries().begin() ; it != r.entries().end() ; ++it)
1210     {
1211         QList<HistoryImageId>::iterator hit;
1212 
1213         for (hit = it->referredImages.begin() ; hit != it->referredImages.end() ; )
1214         {
1215             QFileInfo info(hit->m_filePath + QLatin1Char('/') + hit->m_fileName);
1216 
1217             if (!info.exists())
1218             {
1219                 hit = it->referredImages.erase(hit);
1220             }
1221             else
1222             {
1223                 ++hit;
1224             }
1225         }
1226     }
1227 
1228     return r;
1229 }
1230 
1231 bool EditorWindow::promptUserSave(const QUrl& url, SaveAskMode mode, bool allowCancel)
1232 {
1233     if (d->currentWindowModalDialog)
1234     {
1235         d->currentWindowModalDialog->reject();
1236     }
1237 
1238     if (m_canvas->interface()->undoState().hasUndoableChanges)
1239     {
1240         // if window is minimized, show it
1241 
1242         unminimizeAndActivateWindow();
1243 
1244         bool shallSave    = true;
1245         bool shallDiscard = false;
1246         bool newVersion   = false;
1247 
1248         if (mode == AskIfNeeded)
1249         {
1250             if (m_nonDestructive)
1251             {
1252                 if (versionManager()->settings().editorClosingMode == VersionManagerSettings::AutoSave)
1253                 {
1254                     shallSave = true;
1255                 }
1256                 else
1257                 {
1258                     QPointer<VersioningPromptUserSaveDialog> dialog = new VersioningPromptUserSaveDialog(this);
1259 
1260                     if (dialog->exec() == QDialog::Rejected)
1261                     {
1262                         return false;
1263                     }
1264 
1265                     shallSave    = dialog->shallSave() || dialog->newVersion();
1266                     shallDiscard = dialog->shallDiscard();
1267                     newVersion   = dialog->newVersion();
1268                 }
1269             }
1270             else
1271             {
1272                 QString boxMessage;
1273                 boxMessage = i18nc("@info",
1274                                    "The image \"%1\" has been modified.\n"
1275                                    "Do you want to save it?", url.fileName());
1276 
1277                 int result;
1278 
1279                 if (allowCancel)
1280                 {
1281                     result = QMessageBox::warning(this, qApp->applicationName(), boxMessage,
1282                                                   QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
1283                 }
1284                 else
1285                 {
1286                     result = QMessageBox::warning(this, qApp->applicationName(), boxMessage,
1287                                                   QMessageBox::Save | QMessageBox::Discard);
1288                 }
1289 
1290                 shallSave    = (result == QMessageBox::Save);
1291                 shallDiscard = (result == QMessageBox::Discard);
1292             }
1293         }
1294 
1295         if (shallSave)
1296         {
1297             bool saving = false;
1298 
1299             switch (mode)
1300             {
1301                 case AskIfNeeded:
1302                 {
1303                     if (m_nonDestructive)
1304                     {
1305                         if (newVersion)
1306                         {
1307                             saving = saveNewVersion();
1308                         }
1309                         else
1310                         {
1311                             // will know on its own if new version is required
1312 
1313                             saving = saveCurrentVersion();
1314                         }
1315                     }
1316                     else
1317                     {
1318                         if      (m_canvas->isReadOnly())
1319                         {
1320                             saving = saveAs();
1321                         }
1322                         else if (promptForOverWrite())
1323                         {
1324                             saving = save();
1325                         }
1326                     }
1327 
1328                     break;
1329                 }
1330 
1331                 case OverwriteWithoutAsking:
1332                 {
1333                     if (m_nonDestructive)
1334                     {
1335                         if (newVersion)
1336                         {
1337                             saving = saveNewVersion();
1338                         }
1339                         else
1340                         {
1341                             // will know on its own if new version is required
1342 
1343                             saving = saveCurrentVersion();
1344                         }
1345                     }
1346                     else
1347                     {
1348                         if (m_canvas->isReadOnly())
1349                         {
1350                             saving = saveAs();
1351                         }
1352                         else
1353                         {
1354                             saving = save();
1355                         }
1356                     }
1357 
1358                     break;
1359                 }
1360 
1361                 case AlwaysSaveAs:
1362                 {
1363                     if (m_nonDestructive)
1364                     {
1365                         saving = saveNewVersion();
1366                     }
1367                     else
1368                     {
1369                         saving = saveAs();
1370                     }
1371 
1372                     break;
1373                 }
1374             }
1375 
1376             // save and saveAs return false if they were canceled and did not enter saving at all
1377             // In this case, do not call enterWaitingLoop because quitWaitingloop will not be called.
1378 
1379             if (saving)
1380             {
1381                 // Waiting for asynchronous image file saving operation running in separate thread.
1382 
1383                 m_savingContext.synchronizingState = SavingContext::SynchronousSaving;
1384                 enterWaitingLoop();
1385                 m_savingContext.synchronizingState = SavingContext::NormalSaving;
1386 
1387                 return m_savingContext.synchronousSavingResult;
1388             }
1389             else
1390             {
1391                 return false;
1392             }
1393         }
1394         else if (shallDiscard)
1395         {
1396             // Discard
1397 
1398             m_saveAction->setEnabled(false);
1399 
1400             return true;
1401         }
1402         else
1403         {
1404             return false;
1405         }
1406     }
1407 
1408     return true;
1409 }
1410 
1411 bool EditorWindow::promptUserDelete(const QUrl& url)
1412 {
1413     if (d->currentWindowModalDialog)
1414     {
1415         d->currentWindowModalDialog->reject();
1416     }
1417 
1418     if (m_canvas->interface()->undoState().hasUndoableChanges)
1419     {
1420         // if window is minimized, show it
1421 
1422         unminimizeAndActivateWindow();
1423 
1424         QString boxMessage = i18nc("@info",
1425                                    "The image \"%1\" has been modified.\n"
1426                                    "All changes will be lost.", url.fileName());
1427 
1428         int result = DMessageBox::showContinueCancel(QMessageBox::Warning,
1429                                                      this,
1430                                                      QString(),
1431                                                      boxMessage);
1432 
1433         if (result == QMessageBox::Cancel)
1434         {
1435             return false;
1436         }
1437     }
1438 
1439     return true;
1440 }
1441 
1442 bool EditorWindow::waitForSavingToComplete()
1443 {
1444     // avoid reentrancy - return false means we have reentered the loop already.
1445 
1446     if (m_savingContext.synchronizingState == SavingContext::SynchronousSaving)
1447     {
1448         return false;
1449     }
1450 
1451     if (m_savingContext.savingState != SavingContext::SavingStateNone)
1452     {
1453         // Waiting for asynchronous image file saving operation running in separate thread.
1454 
1455         m_savingContext.synchronizingState = SavingContext::SynchronousSaving;
1456 
1457         enterWaitingLoop();
1458         m_savingContext.synchronizingState = SavingContext::NormalSaving;
1459     }
1460 
1461     return true;
1462 }
1463 
1464 void EditorWindow::enterWaitingLoop()
1465 {
1466     //d->waitingLoop->exec(QEventLoop::ExcludeUserInputEvents);
1467 
1468     execSavingProgressDialog();
1469 }
1470 
1471 void EditorWindow::quitWaitingLoop()
1472 {
1473     //d->waitingLoop->quit();
1474 
1475     if (m_savingProgressDialog)
1476     {
1477         m_savingProgressDialog->close();
1478     }
1479 }
1480 
1481 void EditorWindow::slotSelected(bool val)
1482 {
1483     // Update menu actions.
1484 
1485     d->cropAction->setEnabled(val);
1486     d->zoomFitToSelectAction->setEnabled(val);
1487     d->copyAction->setEnabled(val);
1488 
1489     QRect sel = m_canvas->getSelectedArea();
1490 
1491     // Update histogram into sidebar.
1492 
1493     Q_EMIT signalSelectionChanged(sel);
1494 
1495     // Update status bar
1496 
1497     if (val)
1498     {
1499         slotSelectionSetText(sel);
1500     }
1501     else
1502     {
1503         setToolInfoMessage(i18nc("@info", "No selection"));
1504     }
1505 }
1506 
1507 void EditorWindow::slotPrepareToLoad()
1508 {
1509     // Disable actions as appropriate during loading
1510 
1511     Q_EMIT signalNoCurrentItem();
1512     unsetCursor();
1513     m_animLogo->stop();
1514     toggleActions(false);
1515     slotUpdateItemInfo();
1516 }
1517 
1518 void EditorWindow::slotLoadingStarted(const QString& /*filename*/)
1519 {
1520     setCursor(Qt::WaitCursor);
1521     toggleActions(false);
1522     m_animLogo->start();
1523     m_nameLabel->setProgressBarMode(StatusProgressBar::ProgressBarMode, i18nc("@label", "Loading:"));
1524 }
1525 
1526 void EditorWindow::slotLoadingFinished(const QString& filename, bool success)
1527 {
1528     m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
1529 
1530     // Enable actions as appropriate after loading
1531     // No need to re-enable image properties sidebar here, it's will be done
1532     // automatically by a signal from canvas
1533 
1534     toggleActions(success);
1535     slotUpdateItemInfo();
1536     unsetCursor();
1537     m_animLogo->stop();
1538 
1539     if (success)
1540     {
1541         colorManage();
1542 
1543         // Set a history which contains all available files as referredImages
1544 
1545         DImageHistory resolved = resolvedImageHistory(m_canvas->interface()->getInitialImageHistory());
1546         m_canvas->interface()->setResolvedInitialHistory(resolved);
1547     }
1548     else
1549     {
1550         DNotificationPopup::message(DNotificationPopup::Boxed,
1551                                     i18nc("@info", "Cannot load \"%1\"", filename),
1552                                     m_canvas, m_canvas->mapToGlobal(QPoint(30, 30)));
1553     }
1554 }
1555 
1556 void EditorWindow::resetOrigin()
1557 {
1558     // With versioning, "only" resetting undo history does not work anymore
1559     // as we calculate undo state based on the initial history stored in the DImg
1560 
1561     resetOriginSwitchFile();
1562 }
1563 
1564 void EditorWindow::resetOriginSwitchFile()
1565 {
1566     DImageHistory resolved = resolvedImageHistory(m_canvas->interface()->getItemHistory());
1567     m_canvas->interface()->switchToLastSaved(resolved);
1568 }
1569 
1570 void EditorWindow::colorManage()
1571 {
1572     if (!IccSettings::instance()->isEnabled())
1573     {
1574         return;
1575     }
1576 
1577     DImg image = m_canvas->currentImage();
1578 
1579     if (image.isNull())
1580     {
1581         return;
1582     }
1583 
1584     if (!IccManager::needsPostLoadingManagement(image))
1585     {
1586         return;
1587     }
1588 
1589     IccPostLoadingManager manager(image, m_canvas->currentImageFilePath());
1590 
1591     if (!manager.hasValidWorkspace())
1592     {
1593         QString message = i18nc("@info", "Cannot open the specified working space profile (\"%1\"). "
1594                                 "No color transformation will be applied. "
1595                                 "Please check the color management "
1596                                 "configuration in digiKam's setup.",
1597                                 IccSettings::instance()->settings().workspaceProfile);
1598         QMessageBox::information(this, qApp->applicationName(), message);
1599     }
1600 
1601     // Show dialog and get transform from user choice
1602 
1603     IccTransform trans = manager.postLoadingManage(this);
1604 
1605     // apply transform in thread.
1606     // Do _not_ test for willHaveEffect() here - there are more side effects when calling this method
1607 
1608     m_canvas->applyTransform(trans);
1609     slotUpdateItemInfo();
1610 }
1611 
1612 void EditorWindow::slotNameLabelCancelButtonPressed()
1613 {
1614     // If we saving an image...
1615 
1616     if (m_savingContext.savingState != SavingContext::SavingStateNone)
1617     {
1618         m_savingContext.abortingSaving = true;
1619         m_canvas->abortSaving();
1620     }
1621 }
1622 
1623 void EditorWindow::slotFileOriginChanged(const QString&)
1624 {
1625     // implemented in subclass
1626 }
1627 
1628 bool EditorWindow::saveOrSaveAs()
1629 {
1630     if (m_canvas->isReadOnly())
1631     {
1632         return saveAs();
1633     }
1634 
1635     return save();
1636 }
1637 
1638 void EditorWindow::slotSavingStarted(const QString& /*filename*/)
1639 {
1640     setCursor(Qt::WaitCursor);
1641     m_animLogo->start();
1642 
1643     // Disable actions as appropriate during saving
1644 
1645     Q_EMIT signalNoCurrentItem();
1646 
1647     toggleActions(false);
1648 
1649     m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode,
1650                                     i18nc("@label: saving progress on status bar", "Saving:"));
1651 }
1652 
1653 void EditorWindow::slotSavingFinished(const QString& filename, bool success)
1654 {
1655     Q_UNUSED(filename);
1656 
1657     qCDebug(DIGIKAM_GENERAL_LOG) << filename << success
1658                                  << (m_savingContext.savingState != SavingContext::SavingStateNone);
1659 
1660     // only handle this if we really wanted to save a file...
1661 
1662     if (m_savingContext.savingState != SavingContext::SavingStateNone)
1663     {
1664         m_savingContext.executedOperation = m_savingContext.savingState;
1665         m_savingContext.savingState = SavingContext::SavingStateNone;
1666 
1667         if (!success)
1668         {
1669             if (!m_savingContext.abortingSaving)
1670             {
1671                 QMessageBox::critical(this, qApp->applicationName(),
1672                                       i18nc("@info", "Failed to save file\n\"%1\"\nto\n\"%2\".",
1673                                             m_savingContext.destinationURL.fileName(),
1674                                             m_savingContext.destinationURL.toLocalFile()));
1675             }
1676 
1677             finishSaving(false);
1678             return;
1679         }
1680 
1681         moveFile();
1682 
1683     }
1684     else
1685     {
1686         qCWarning(DIGIKAM_GENERAL_LOG) << "Why was slotSavingFinished called if we did not want to save a file?";
1687     }
1688 }
1689 
1690 void EditorWindow::movingSaveFileFinished(bool successful)
1691 {
1692     if (!successful)
1693     {
1694         finishSaving(false);
1695         return;
1696     }
1697 
1698     // now that we know the real destination file name, pass it to be recorded in image history
1699 
1700     m_canvas->interface()->setLastSaved(m_savingContext.destinationURL.toLocalFile());
1701 
1702     // remove image from cache since it has changed
1703 
1704     LoadingCacheInterface::fileChanged(m_savingContext.destinationURL.toLocalFile());
1705     ThumbnailLoadThread::deleteThumbnail(m_savingContext.destinationURL.toLocalFile());
1706 
1707     // restore state of disabled actions. saveIsComplete can start any other task
1708     // (loading!) which might itself in turn change states
1709 
1710     finishSaving(true);
1711 
1712     switch (m_savingContext.executedOperation)
1713     {
1714         case SavingContext::SavingStateNone:
1715         {
1716             break;
1717         }
1718 
1719         case SavingContext::SavingStateSave:
1720         {
1721             saveIsComplete();
1722             break;
1723         }
1724 
1725         case SavingContext::SavingStateSaveAs:
1726         {
1727             saveAsIsComplete();
1728             break;
1729         }
1730 
1731         case SavingContext::SavingStateVersion:
1732         {
1733             saveVersionIsComplete();
1734             break;
1735         }
1736     }
1737 
1738     // Take all actions necessary to update information and re-enable sidebar
1739 
1740     slotChanged();
1741 }
1742 
1743 void EditorWindow::finishSaving(bool success)
1744 {
1745     m_savingContext.synchronousSavingResult = success;
1746 
1747     delete m_savingContext.saveTempFile;
1748     m_savingContext.saveTempFile = nullptr;
1749 
1750     // Exit of internal Qt event loop to unlock promptUserSave() method.
1751 
1752     if (m_savingContext.synchronizingState == SavingContext::SynchronousSaving)
1753     {
1754         quitWaitingLoop();
1755     }
1756 
1757     // Enable actions as appropriate after saving
1758 
1759     toggleActions(true);
1760     unsetCursor();
1761     m_animLogo->stop();
1762 
1763     m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
1764 /*
1765     if (m_savingProgressDialog)
1766     {
1767         m_savingProgressDialog->close();
1768     }
1769 */
1770 
1771     // On error, continue using current image
1772 
1773     if (!success)
1774     {
1775         /* Why this?
1776          * m_canvas->switchToLastSaved(m_savingContext.srcURL.toLocalFile());
1777          */
1778     }
1779 }
1780 
1781 void EditorWindow::setupTempSaveFile(const QUrl& url)
1782 {
1783     // if the destination url is on local file system, try to set the temp file
1784     // location to the destination folder, otherwise use a local default
1785 
1786     QString tempDir = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile();
1787 
1788     if (!url.isLocalFile() || tempDir.isEmpty())
1789     {
1790         tempDir = QDir::tempPath();
1791     }
1792 
1793     QFileInfo fi(url.toLocalFile());
1794     QString suffix = fi.suffix();
1795 
1796     // use magic file extension which tells the digikamalbums ioslave to ignore the file
1797 
1798     m_savingContext.saveTempFile = new SafeTemporaryFile(tempDir + QLatin1String("/EditorWindow-XXXXXX"
1799                                                                                  ".digikamtempfile.") + suffix);
1800     m_savingContext.saveTempFile->setAutoRemove(false);
1801 
1802     if (!m_savingContext.saveTempFile->open())
1803     {
1804         QMessageBox::critical(this, qApp->applicationName(),
1805                               i18nc("@info", "Could not open a temporary file in the folder \"%1\": %2 (%3)",
1806                                     QDir::toNativeSeparators(tempDir), m_savingContext.saveTempFile->errorString(),
1807                                     m_savingContext.saveTempFile->error()));
1808         return;
1809     }
1810 
1811     m_savingContext.saveTempFileName = m_savingContext.saveTempFile->safeFilePath();
1812     delete m_savingContext.saveTempFile;
1813     m_savingContext.saveTempFile = nullptr;
1814 }
1815 
1816 void EditorWindow::startingSave(const QUrl& url)
1817 {
1818     qCDebug(DIGIKAM_GENERAL_LOG) << "startSaving url = " << url;
1819 
1820     // avoid any reentrancy. Should be impossible anyway since actions will be disabled.
1821 
1822     if (m_savingContext.savingState != SavingContext::SavingStateNone)
1823     {
1824         return;
1825     }
1826 
1827     m_savingContext = SavingContext();
1828 
1829     if (!checkPermissions(url))
1830     {
1831         return;
1832     }
1833 
1834     setupTempSaveFile(url);
1835 
1836     m_savingContext.srcURL             = url;
1837     m_savingContext.destinationURL     = m_savingContext.srcURL;
1838     m_savingContext.destinationExisted = true;
1839     m_savingContext.originalFormat     = m_canvas->currentImageFileFormat();
1840     m_savingContext.format             = m_savingContext.originalFormat;
1841     m_savingContext.abortingSaving     = false;
1842     m_savingContext.savingState        = SavingContext::SavingStateSave;
1843     m_savingContext.executedOperation  = SavingContext::SavingStateNone;
1844 
1845     m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
1846                                   m_setExifOrientationTag && m_canvas->exifRotated(), m_savingContext.format,
1847                                   m_savingContext.destinationURL.toLocalFile());
1848 }
1849 
1850 bool EditorWindow::showFileSaveDialog(const QUrl& initialUrl, QUrl& newURL)
1851 {
1852     QString all;
1853     QStringList list                          = supportedImageMimeTypes(QIODevice::WriteOnly, all);
1854     QPointer<DFileDialog> imageFileSaveDialog = new DFileDialog(this);
1855     imageFileSaveDialog->setWindowTitle(i18nc("@title:window", "New Image File Name"));
1856     imageFileSaveDialog->setDirectoryUrl(initialUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
1857     imageFileSaveDialog->setOption(QFileDialog::DontConfirmOverwrite);
1858     imageFileSaveDialog->setAcceptMode(QFileDialog::AcceptSave);
1859     imageFileSaveDialog->setFileMode(QFileDialog::AnyFile);
1860     imageFileSaveDialog->setNameFilters(list);
1861 
1862     // Restore old settings for the dialog
1863 
1864     KSharedConfig::Ptr config         = KSharedConfig::openConfig();
1865     KConfigGroup group                = config->group(configGroupName());
1866     const QString optionLastExtension = QLatin1String("LastSavedImageExtension");
1867     QString ext                       = group.readEntry(optionLastExtension, "png");
1868 
1869     Q_FOREACH (const QString& s, list)
1870     {
1871         if (s.contains(QString::fromLatin1("*.%1").arg(ext)))
1872         {   // cppcheck-suppress useStlAlgorithm
1873             imageFileSaveDialog->selectNameFilter(s);
1874             break;
1875         }
1876     }
1877 
1878     // Adjust extension of proposed filename
1879 
1880     QString fileName             = initialUrl.fileName();
1881 
1882     if (!fileName.isNull())
1883     {
1884         int lastDot              = fileName.lastIndexOf(QLatin1Char('.'));
1885         QString completeBaseName = (lastDot == -1) ? fileName : fileName.left(lastDot);
1886         fileName                 = completeBaseName + QLatin1Char('.') + ext;
1887     }
1888 
1889     if (!fileName.isNull())
1890     {
1891         imageFileSaveDialog->selectFile(fileName);
1892     }
1893 
1894     // Start dialog and check if canceled.
1895 
1896     if (d->currentWindowModalDialog)
1897     {
1898         // go application-modal - we will create utter confusion if descending into more than one window-modal dialog
1899 
1900         imageFileSaveDialog->setModal(true);
1901         imageFileSaveDialog->exec();
1902     }
1903     else
1904     {
1905         imageFileSaveDialog->setWindowModality(Qt::WindowModal);
1906         d->currentWindowModalDialog = imageFileSaveDialog;
1907         imageFileSaveDialog->exec();
1908         d->currentWindowModalDialog = nullptr;
1909     }
1910 
1911     qApp->processEvents();
1912 
1913     if (!imageFileSaveDialog || !imageFileSaveDialog->hasAcceptedUrls())
1914     {
1915         qCDebug(DIGIKAM_GENERAL_LOG) << "File Save Dialog rejected";
1916         delete imageFileSaveDialog;
1917 
1918         return false;
1919     }
1920 
1921     newURL = imageFileSaveDialog->selectedUrls().first();
1922     newURL.setPath(QDir::cleanPath(newURL.path()));
1923 
1924     QFileInfo fi(newURL.fileName());
1925 
1926     if (fi.suffix().isEmpty())
1927     {
1928         ext = imageFileSaveDialog->selectedNameFilter().section(QLatin1String("*."), 1, 1);
1929         ext = ext.left(ext.length() - 1);
1930 
1931         if (ext.isEmpty())
1932         {
1933             ext = QLatin1String("jpg");
1934         }
1935 
1936         newURL.setPath(newURL.path() + QLatin1Char('.') + ext);
1937     }
1938 
1939     delete imageFileSaveDialog;
1940 
1941     qCDebug(DIGIKAM_GENERAL_LOG) << "Writing file to " << newURL;
1942 
1943     //-- Show Settings Dialog ----------------------------------------------
1944 
1945     const QString configShowImageSettingsDialog = QLatin1String("ShowImageSettingsDialog");
1946     bool showDialog                             = group.readEntry(configShowImageSettingsDialog, true);
1947     QPointer<FileSaveOptionsBox> options        = new FileSaveOptionsBox();
1948 
1949     if (showDialog && (options->discoverFormat(newURL.fileName(), FileSaveOptionsBox::NONE) != FileSaveOptionsBox::NONE))
1950     {
1951         QPointer<FileSaveOptionsDlg> fileSaveOptionsDialog = new FileSaveOptionsDlg(this, options);
1952         options->setImageFileFormat(newURL.fileName());
1953         int result;
1954 
1955         if (d->currentWindowModalDialog)
1956         {
1957             // go application-modal - we will create utter confusion if descending into more than one window-modal dialog
1958 
1959             fileSaveOptionsDialog->setModal(true);
1960             result = fileSaveOptionsDialog->exec();
1961         }
1962         else
1963         {
1964             fileSaveOptionsDialog->setWindowModality(Qt::WindowModal);
1965             d->currentWindowModalDialog = fileSaveOptionsDialog;
1966             result                      = fileSaveOptionsDialog->exec();
1967             d->currentWindowModalDialog = nullptr;
1968         }
1969 
1970         if ((result != QDialog::Accepted) || !fileSaveOptionsDialog)
1971         {
1972             delete fileSaveOptionsDialog;
1973 
1974             return false;
1975         }
1976 
1977         // Write settings to config
1978 
1979         options->applySettings();
1980 
1981         delete fileSaveOptionsDialog;
1982 
1983         // Options is now also deleted
1984     }
1985     else
1986     {
1987         delete options;
1988     }
1989 
1990     // Read settings from config to local container
1991 
1992     applyIOSettings();
1993 
1994     // Select the format to save the image with
1995 
1996     m_savingContext.format = selectValidSavingFormat(newURL);
1997 
1998     if (m_savingContext.format.isNull())
1999     {
2000         QMessageBox::critical(this, qApp->applicationName(),
2001                               i18nc("@info", "Unable to determine the format to save the target image with."));
2002         return false;
2003     }
2004 
2005     if (!newURL.isValid())
2006     {
2007         QMessageBox::critical(this, qApp->applicationName(),
2008                               i18nc("@info", "Cannot Save: Found file path \"%1\" is invalid.", newURL.toDisplayString()));
2009         qCWarning(DIGIKAM_GENERAL_LOG) << "target URL is not valid !";
2010         return false;
2011     }
2012 
2013     group.writeEntry(optionLastExtension, m_savingContext.format);
2014     config->sync();
2015 
2016     return true;
2017 }
2018 
2019 QString EditorWindow::selectValidSavingFormat(const QUrl& targetUrl)
2020 {
2021     qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to find a saving format from targetUrl = " << targetUrl;
2022 
2023     // Build a list of valid types
2024 
2025     QString all;
2026     supportedImageMimeTypes(QIODevice::WriteOnly, all);
2027     qCDebug(DIGIKAM_GENERAL_LOG) << "Qt Offered types: " << all;
2028 
2029     QStringList validTypes = all.split(QLatin1String("*."), QT_SKIP_EMPTY_PARTS);
2030     validTypes.replaceInStrings(QLatin1String(" "), QString());
2031 
2032     qCDebug(DIGIKAM_GENERAL_LOG) << "Writable formats: " << validTypes;
2033 
2034     // Determine the format to use the format provided in the filename
2035 
2036     QString suffix;
2037 
2038     if (targetUrl.isLocalFile())
2039     {
2040         // For local files QFileInfo can be used
2041 
2042         QFileInfo fi(targetUrl.toLocalFile());
2043         suffix = fi.suffix();
2044         qCDebug(DIGIKAM_GENERAL_LOG) << "Possible format from local file: " << suffix;
2045     }
2046     else
2047     {
2048         // For remote files string manipulation is needed unfortunately
2049 
2050         QString fileName         = targetUrl.fileName();
2051         const int periodLocation = fileName.lastIndexOf(QLatin1Char('.'));
2052 
2053         if (periodLocation >= 0)
2054         {
2055             suffix = fileName.right(fileName.size() - periodLocation - 1);
2056         }
2057 
2058         qCDebug(DIGIKAM_GENERAL_LOG) << "Possible format from remote file: " << suffix;
2059     }
2060 
2061     if (!suffix.isEmpty() && validTypes.contains(suffix, Qt::CaseInsensitive))
2062     {
2063         qCDebug(DIGIKAM_GENERAL_LOG) << "Using format from target url " << suffix;
2064         return suffix;
2065     }
2066 
2067     // Another way to determine the format is to use the original file
2068     {
2069         QString originalFormat = QString::fromUtf8(QImageReader::imageFormat(m_savingContext.srcURL.toLocalFile()));
2070 
2071         if (validTypes.contains(originalFormat, Qt::CaseInsensitive))
2072         {
2073             qCDebug(DIGIKAM_GENERAL_LOG) << "Using format from original file: " << originalFormat;
2074             return originalFormat;
2075         }
2076     }
2077 
2078     qCDebug(DIGIKAM_GENERAL_LOG) << "No suitable format found";
2079 
2080     return QString();
2081 }
2082 
2083 bool EditorWindow::startingSaveAs(const QUrl& url)
2084 {
2085     qCDebug(DIGIKAM_GENERAL_LOG) << "startSavingAs called";
2086 
2087     if (m_savingContext.savingState != SavingContext::SavingStateNone)
2088     {
2089         return false;
2090     }
2091 
2092     m_savingContext        = SavingContext();
2093     m_savingContext.srcURL = url;
2094     QUrl suggested         = m_savingContext.srcURL;
2095 
2096     // Run dialog -------------------------------------------------------------------
2097 
2098     QUrl newURL;
2099 
2100     if (!showFileSaveDialog(suggested, newURL))
2101     {
2102         return false;
2103     }
2104 
2105     // If new and original URL are equal use save() ------------------------------
2106 
2107     QUrl currURL(m_savingContext.srcURL);
2108     currURL.setPath(QDir::cleanPath(currURL.path()));
2109     newURL.setPath(QDir::cleanPath(newURL.path()));
2110 
2111     if (currURL.matches(newURL, QUrl::None))
2112     {
2113         save();
2114         return false;
2115     }
2116 
2117     // Check for overwrite ----------------------------------------------------------
2118 
2119     QFileInfo fi(newURL.toLocalFile());
2120     m_savingContext.destinationExisted = fi.exists();
2121 
2122     if (m_savingContext.destinationExisted)
2123     {
2124         if (!checkOverwrite(newURL))
2125         {
2126             return false;
2127         }
2128 
2129         // There will be two message boxes if the file is not writable.
2130         // This may be controversial, and it may be changed, but it was a deliberate decision.
2131 
2132         if (!checkPermissions(newURL))
2133         {
2134             return false;
2135         }
2136     }
2137 
2138     // Now do the actual saving -----------------------------------------------------
2139 
2140     setupTempSaveFile(newURL);
2141 
2142     m_savingContext.destinationURL = newURL;
2143     m_savingContext.originalFormat = m_canvas->currentImageFileFormat();
2144     m_savingContext.savingState    = SavingContext::SavingStateSaveAs;
2145     m_savingContext.executedOperation = SavingContext::SavingStateNone;
2146     m_savingContext.abortingSaving = false;
2147 
2148     // In any case, destructive (Save as) or non (Export), mark as New Version
2149 
2150     m_canvas->interface()->setHistoryIsBranch(true);
2151 
2152     m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
2153                                   m_setExifOrientationTag && m_canvas->exifRotated(),
2154                                   m_savingContext.format.toLower(),
2155                                   m_savingContext.destinationURL.toLocalFile());
2156 
2157     return true;
2158 }
2159 
2160 bool EditorWindow::startingSaveCurrentVersion(const QUrl& url)
2161 {
2162     return startingSaveVersion(url, false, false, QString());
2163 }
2164 
2165 bool EditorWindow::startingSaveNewVersion(const QUrl& url)
2166 {
2167     return startingSaveVersion(url, true, false, QString());
2168 }
2169 
2170 bool EditorWindow::startingSaveNewVersionAs(const QUrl& url)
2171 {
2172     return startingSaveVersion(url, true, true, QString());
2173 }
2174 
2175 bool EditorWindow::startingSaveNewVersionInFormat(const QUrl& url, const QString& format)
2176 {
2177     return startingSaveVersion(url, true, false, format);
2178 }
2179 
2180 VersionFileOperation EditorWindow::saveVersionFileOperation(const QUrl& url, bool fork)
2181 {
2182     DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
2183     DImageHistory history         = m_canvas->interface()->getItemHistory();
2184 
2185     VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
2186                                 url.fileName(), m_canvas->currentImageFileFormat());
2187 
2188     return versionManager()->operation(fork ? VersionManager::NewVersionName : VersionManager::CurrentVersionName,
2189                                        currentName, resolvedHistory, history);
2190 }
2191 
2192 VersionFileOperation EditorWindow::saveAsVersionFileOperation(const QUrl& url, const QUrl& saveUrl, const QString& format)
2193 {
2194     DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
2195     DImageHistory history         = m_canvas->interface()->getItemHistory();
2196 
2197     VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
2198                                 url.fileName(), m_canvas->currentImageFileFormat());
2199 
2200     VersionFileInfo saveLocation(saveUrl.adjusted(QUrl::RemoveFilename).toLocalFile(),
2201                                  saveUrl.fileName(), format);
2202 
2203     return versionManager()->operationNewVersionAs(currentName, saveLocation, resolvedHistory, history);
2204 }
2205 
2206 VersionFileOperation EditorWindow::saveInFormatVersionFileOperation(const QUrl& url, const QString& format)
2207 {
2208     DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
2209     DImageHistory history         = m_canvas->interface()->getItemHistory();
2210 
2211     VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
2212                                 url.fileName(), m_canvas->currentImageFileFormat());
2213 
2214     return versionManager()->operationNewVersionInFormat(currentName, format, resolvedHistory, history);
2215 }
2216 
2217 bool EditorWindow::startingSaveVersion(const QUrl& url, bool fork, bool saveAs, const QString& format)
2218 {
2219     qCDebug(DIGIKAM_GENERAL_LOG) << "Saving image" << url << "non-destructive, new version:"
2220                                  << fork << ", saveAs:" << saveAs << "format:" << format;
2221 
2222     if (m_savingContext.savingState != SavingContext::SavingStateNone)
2223     {
2224         return false;
2225     }
2226 
2227     m_savingContext                      = SavingContext();
2228     m_savingContext.versionFileOperation = saveVersionFileOperation(url, fork);
2229     m_canvas->interface()->setHistoryIsBranch(fork);
2230 
2231     if (saveAs)
2232     {
2233         QUrl suggested = m_savingContext.versionFileOperation.saveFile.fileUrl();
2234         QUrl selectedUrl;
2235 
2236         if (!showFileSaveDialog(suggested, selectedUrl))
2237         {
2238             return false;
2239         }
2240 
2241         m_savingContext.versionFileOperation = saveAsVersionFileOperation(url, selectedUrl, m_savingContext.format);
2242     }
2243     else if (!format.isNull())
2244     {
2245         m_savingContext.versionFileOperation = saveInFormatVersionFileOperation(url, format);
2246     }
2247 
2248     const QUrl newURL = m_savingContext.versionFileOperation.saveFile.fileUrl();
2249     qCDebug(DIGIKAM_GENERAL_LOG) << "Writing file to " << newURL;
2250 
2251     if (!newURL.isValid())
2252     {
2253         QMessageBox::critical(this, qApp->applicationName(),
2254                               i18nc("@info",
2255                                     "Cannot save file \"%1\" to "
2256                                     "the suggested version file name \"%2\"",
2257                                     url.fileName(),
2258                                     newURL.fileName()));
2259         qCWarning(DIGIKAM_GENERAL_LOG) << "target URL is not valid !";
2260 
2261         return false;
2262     }
2263 
2264     QFileInfo fi(newURL.toLocalFile());
2265     m_savingContext.destinationExisted = fi.exists();
2266 
2267     // Check for overwrite (saveAs only) --------------------------------------------
2268 
2269     if (m_savingContext.destinationExisted)
2270     {
2271         // So, should we refuse to overwrite the original?
2272         // It's a frontal crash against non-destructive principles.
2273         // It is tempting to refuse, yet I think the user has to decide in the end
2274 /*
2275         QUrl currURL(m_savingContext.srcURL);
2276         currURL.cleanPath();
2277         newURL.cleanPath();
2278         if (currURL.equals(newURL))
2279         {
2280             ...
2281             return false;
2282         }
2283 */
2284 
2285         // check for overwrite, unless the operation explicitly tells us to overwrite
2286 
2287         if (!(m_savingContext.versionFileOperation.tasks & VersionFileOperation::Replace) &&
2288             !checkOverwrite(newURL))
2289         {
2290             return false;
2291         }
2292 
2293         // There will be two message boxes if the file is not writable.
2294         // This may be controversial, and it may be changed, but it was a deliberate decision.
2295 
2296         if (!checkPermissions(newURL))
2297         {
2298             return false;
2299         }
2300     }
2301 
2302     setupTempSaveFile(newURL);
2303 
2304     m_savingContext.srcURL             = url;
2305     m_savingContext.destinationURL     = newURL;
2306     m_savingContext.originalFormat     = m_canvas->currentImageFileFormat();
2307     m_savingContext.format             = m_savingContext.versionFileOperation.saveFile.format;
2308     m_savingContext.abortingSaving     = false;
2309     m_savingContext.savingState        = SavingContext::SavingStateVersion;
2310     m_savingContext.executedOperation  = SavingContext::SavingStateNone;
2311 
2312     m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
2313                                   m_setExifOrientationTag && m_canvas->exifRotated(),
2314                                   m_savingContext.format.toLower(),
2315                                   m_savingContext.versionFileOperation);
2316 
2317     return true;
2318 }
2319 
2320 bool EditorWindow::checkPermissions(const QUrl& url)
2321 {
2322     // TODO: Check that the permissions can actually be changed
2323     //       if write permissions are not available.
2324 
2325     QFileInfo fi(url.toLocalFile());
2326 
2327     if (fi.exists() && !fi.isWritable())
2328     {
2329         int result = QMessageBox::warning(this, i18nc("@title:window", "Overwrite File?"),
2330                                           i18nc("@info", "You do not have write permissions "
2331                                                 "for the file named \"%1\". "
2332                                                 "Are you sure you want "
2333                                                 "to overwrite it?",
2334                                                 url.fileName()),
2335                                           QMessageBox::Save | QMessageBox::Cancel);
2336 
2337         if (result != QMessageBox::Save)
2338         {
2339             return false;
2340         }
2341     }
2342 
2343     return true;
2344 }
2345 
2346 bool EditorWindow::checkOverwrite(const QUrl& url)
2347 {
2348     int result = QMessageBox::warning(this, i18nc("@title:window", "Overwrite File?"),
2349                                       i18nc("@info", "A file named \"%1\" already "
2350                                             "exists. Are you sure you want "
2351                                             "to overwrite it?",
2352                                             url.fileName()),
2353                                       QMessageBox::Save | QMessageBox::Cancel);
2354 
2355     return (result == QMessageBox::Save);
2356 }
2357 
2358 bool EditorWindow::moveLocalFile(const QString& org, const QString& dst)
2359 {
2360     QString sidecarOrg = DMetadata::sidecarPath(org);
2361     QString source     = m_savingContext.srcURL.toLocalFile();
2362 
2363     if (QFileInfo::exists(sidecarOrg))
2364     {
2365         QString sidecarDst = DMetadata::sidecarPath(dst);
2366 
2367         if (!DFileOperations::localFileRename(source, sidecarOrg, sidecarDst))
2368         {
2369             qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to move sidecar file";
2370         }
2371     }
2372 
2373     if (!DFileOperations::localFileRename(source, org, dst))
2374     {
2375         QMessageBox::critical(this, i18nc("@title:window", "Error Saving File"),
2376                               i18nc("@info", "Failed to overwrite original file"));
2377         return false;
2378     }
2379 
2380     return true;
2381 }
2382 
2383 void EditorWindow::moveFile()
2384 {
2385     // Move local file.
2386 
2387     if (m_savingContext.executedOperation == SavingContext::SavingStateVersion)
2388     {
2389         // Check if we need to move the current file to an intermediate name
2390 
2391         if (m_savingContext.versionFileOperation.tasks & VersionFileOperation::MoveToIntermediate)
2392         {
2393             //qCDebug(DIGIKAM_GENERAL_LOG) << "MoveToIntermediate: Moving " << m_savingContext.srcURL.toLocalFile() << "to"
2394             //                             << m_savingContext.versionFileOperation.intermediateForLoadedFile.filePath()
2395 
2396             moveLocalFile(m_savingContext.srcURL.toLocalFile(),
2397                           m_savingContext.versionFileOperation.intermediateForLoadedFile.filePath());
2398 
2399             LoadingCacheInterface::fileChanged(m_savingContext.destinationURL.toLocalFile());
2400             ThumbnailLoadThread::deleteThumbnail(m_savingContext.destinationURL.toLocalFile());
2401         }
2402     }
2403 
2404     bool moveSuccessful = moveLocalFile(m_savingContext.saveTempFileName,
2405                                         m_savingContext.destinationURL.toLocalFile());
2406 
2407     if (m_savingContext.executedOperation == SavingContext::SavingStateVersion)
2408     {
2409         if (moveSuccessful &&
2410             m_savingContext.versionFileOperation.tasks & VersionFileOperation::SaveAndDelete)
2411         {
2412             QFile file(m_savingContext.versionFileOperation.loadedFile.filePath());
2413             file.remove();
2414         }
2415     }
2416 
2417     movingSaveFileFinished(moveSuccessful);
2418 }
2419 
2420 void EditorWindow::slotDiscardChanges()
2421 {
2422     m_canvas->interface()->rollbackToOrigin();
2423 }
2424 
2425 void EditorWindow::slotOpenOriginal()
2426 {
2427     // No-op in this base class
2428 }
2429 
2430 void EditorWindow::slotColorManagementOptionsChanged()
2431 {
2432     applyColorManagementSettings();
2433     applyIOSettings();
2434 }
2435 
2436 void EditorWindow::slotToggleColorManagedView()
2437 {
2438     if (!IccSettings::instance()->isEnabled())
2439     {
2440         return;
2441     }
2442 
2443     bool cmv = !IccSettings::instance()->settings().useManagedView;
2444     IccSettings::instance()->setUseManagedView(cmv);
2445 }
2446 
2447 void EditorWindow::setColorManagedViewIndicatorToolTip(bool available, bool cmv)
2448 {
2449     QString tooltip;
2450 
2451     if (available)
2452     {
2453         if (cmv)
2454         {
2455             tooltip = i18nc("@info", "Color-Managed View is enabled.");
2456         }
2457         else
2458         {
2459             tooltip = i18nc("@info", "Color-Managed View is disabled.");
2460         }
2461     }
2462     else
2463     {
2464         tooltip = i18nc("@info", "Color Management is not configured, so the Color-Managed View is not available.");
2465     }
2466 
2467     d->cmViewIndicator->setToolTip(tooltip);
2468 }
2469 
2470 void EditorWindow::slotSoftProofingOptions()
2471 {
2472     // Adjusts global settings
2473 
2474     QPointer<SoftProofDialog> dlg = new SoftProofDialog(this);
2475     dlg->exec();
2476 
2477     d->viewSoftProofAction->setChecked(dlg->shallEnableSoftProofView());
2478     slotUpdateSoftProofingState();
2479     delete dlg;
2480 }
2481 
2482 void EditorWindow::slotUpdateSoftProofingState()
2483 {
2484     bool on = d->viewSoftProofAction->isChecked();
2485     m_canvas->setSoftProofingEnabled(on);
2486     d->toolIface->updateICCSettings();
2487 }
2488 
2489 void EditorWindow::slotSetUnderExposureIndicator(bool on)
2490 {
2491     d->exposureSettings->underExposureIndicator = on;
2492     d->toolIface->updateExposureSettings();
2493     d->viewUnderExpoAction->setChecked(on);
2494     setUnderExposureToolTip(on);
2495 }
2496 
2497 void EditorWindow::setUnderExposureToolTip(bool on)
2498 {
2499     d->underExposureIndicator->setToolTip(
2500         on ? i18nc("@info", "Under-Exposure indicator is enabled")
2501            : i18nc("@info", "Under-Exposure indicator is disabled"));
2502 }
2503 
2504 void EditorWindow::slotSetOverExposureIndicator(bool on)
2505 {
2506     d->exposureSettings->overExposureIndicator = on;
2507     d->toolIface->updateExposureSettings();
2508     d->viewOverExpoAction->setChecked(on);
2509     setOverExposureToolTip(on);
2510 }
2511 
2512 void EditorWindow::setOverExposureToolTip(bool on)
2513 {
2514     d->overExposureIndicator->setToolTip(
2515         on ? i18nc("@info", "Over-Exposure indicator is enabled")
2516            : i18nc("@info", "Over-Exposure indicator is disabled"));
2517 }
2518 
2519 void EditorWindow::slotSelectionChanged(const QRect& sel)
2520 {
2521     slotSelectionSetText(sel);
2522     Q_EMIT signalSelectionChanged(sel);
2523 }
2524 
2525 void EditorWindow::slotSelectionSetText(const QRect& sel)
2526 {
2527     setToolInfoMessage(QString::fromLatin1("(%1, %2) (%3 x %4)")
2528                        .arg(sel.x())
2529                        .arg(sel.y())
2530                        .arg(sel.width())
2531                        .arg(sel.height()));
2532 }
2533 
2534 void EditorWindow::slotComponentsInfo()
2535 {
2536     LibsInfoDlg* const dlg = new LibsInfoDlg(this);
2537     dlg->show();
2538 }
2539 
2540 void EditorWindow::setToolStartProgress(const QString& toolName)
2541 {
2542     m_animLogo->start();
2543     m_nameLabel->setProgressValue(0);
2544     m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode,
2545                                     QString::fromUtf8("%1:").arg(toolName));
2546 }
2547 
2548 void EditorWindow::setToolProgress(int progress)
2549 {
2550     m_nameLabel->setProgressValue(progress);
2551 }
2552 
2553 void EditorWindow::setToolStopProgress()
2554 {
2555     m_animLogo->stop();
2556     m_nameLabel->setProgressValue(0);
2557     m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
2558     slotUpdateItemInfo();
2559 }
2560 
2561 void EditorWindow::slotCloseTool()
2562 {
2563     if (d->toolIface)
2564     {
2565         d->toolIface->slotCloseTool();
2566     }
2567 }
2568 
2569 void EditorWindow::slotApplyTool()
2570 {
2571     if (d->toolIface)
2572     {
2573         d->toolIface->slotApplyTool();
2574     }
2575 }
2576 
2577 void EditorWindow::setPreviewModeMask(int mask)
2578 {
2579     d->previewToolBar->setPreviewModeMask(mask);
2580 }
2581 
2582 PreviewToolBar::PreviewMode EditorWindow::previewMode() const
2583 {
2584     return d->previewToolBar->previewMode();
2585 }
2586 
2587 void EditorWindow::setToolInfoMessage(const QString& txt)
2588 {
2589     d->infoLabel->setAdjustedText(txt);
2590 }
2591 
2592 VersionManager* EditorWindow::versionManager() const
2593 {
2594     return &d->defaultVersionManager;
2595 }
2596 
2597 void EditorWindow::setupSelectToolsAction()
2598 {
2599     // Create action model
2600 
2601     ActionItemModel* const actionModel = new ActionItemModel(this);
2602     actionModel->setMode((ActionItemModel::MenuCategoryMode)(ActionItemModel::ToplevelMenuCategory |
2603                                                              ActionItemModel::SortCategoriesByInsertionOrder));
2604 
2605     // Builtin actions
2606 
2607     QString transformCategory = i18nc("@title Image Transform",       "Transform");
2608 
2609     // See bug #447687
2610     actionModel->addAction(d->rotateLeftAction,  transformCategory);
2611     actionModel->addAction(d->rotateRightAction, transformCategory);
2612     actionModel->addAction(d->flipHorizAction,   transformCategory);
2613     actionModel->addAction(d->flipVertAction,    transformCategory);
2614     actionModel->addAction(d->cropAction,        transformCategory);
2615 
2616     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorTransform, this))
2617     {
2618         actionModel->addAction(ac, transformCategory);
2619     }
2620 
2621     QString decorateCategory  = i18nc("@title Image Decorate",        "Decorate");
2622 
2623     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorDecorate, this))
2624     {
2625         actionModel->addAction(ac, decorateCategory);
2626     }
2627 
2628     QString effectsCategory   = i18nc("@title Image Effect",          "Effects");
2629 
2630     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorFilters, this))
2631     {
2632         actionModel->addAction(ac, effectsCategory);
2633     }
2634 
2635     QString colorsCategory    = i18nc("@title Image Colors",          "Colors");
2636 
2637     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorColors, this))
2638     {
2639         actionModel->addAction(ac, effectsCategory);
2640     }
2641 
2642     QString enhanceCategory   = i18nc("@title Image Enhance",         "Enhance");
2643 
2644     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorEnhance, this))
2645     {
2646         actionModel->addAction(ac, effectsCategory);
2647     }
2648 
2649     QString postCategory      = i18nc("@title Post Processing Tools", "Post-Processing");
2650 
2651     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::GenericTool, this))
2652     {
2653         actionModel->addAction(ac, postCategory);
2654     }
2655 
2656     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::EditorFile, this))
2657     {
2658         actionModel->addAction(ac, postCategory);
2659     }
2660 
2661     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::GenericMetadata, this))
2662     {
2663         actionModel->addAction(ac, postCategory);
2664     }
2665 
2666     QString exportCategory    = i18nc("@title Export Tools",          "Export");
2667 
2668     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::GenericExport, this))
2669     {
2670         actionModel->addAction(ac, exportCategory);
2671     }
2672 
2673     QString importCategory    = i18nc("@title Import Tools",          "Import");
2674 
2675     Q_FOREACH (DPluginAction* const ac, DPluginLoader::instance()->pluginsActions(DPluginAction::GenericImport, this))
2676     {
2677         actionModel->addAction(ac, importCategory);
2678     }
2679 
2680     // setup categorized view
2681 
2682     DCategorizedSortFilterProxyModel* const filterModel = actionModel->createFilterModel();
2683 
2684     ActionCategorizedView* const selectToolsActionView  = new ActionCategorizedView;
2685     selectToolsActionView->setIconSize(QSize(22, 22));
2686     selectToolsActionView->setModel(filterModel);
2687     selectToolsActionView->setupIconMode();
2688     selectToolsActionView->adjustGridSize();
2689 
2690     connect(selectToolsActionView, SIGNAL(clicked(QModelIndex)),
2691             actionModel, SLOT(trigger(QModelIndex)));
2692 
2693     EditorToolIface::editorToolIface()->setToolsIconView(selectToolsActionView);
2694 }
2695 
2696 void EditorWindow::slotThemeChanged()
2697 {
2698     KSharedConfig::Ptr config = KSharedConfig::openConfig();
2699     KConfigGroup group        = config->group(configGroupName());
2700 
2701     if (!group.readEntry(d->configUseThemeBackgroundColorEntry, true))
2702     {
2703         m_bgColor = group.readEntry(d->configBackgroundColorEntry, QColor(Qt::black));
2704     }
2705     else
2706     {
2707         m_bgColor = palette().color(QPalette::Base);
2708     }
2709 
2710     m_canvas->setBackgroundBrush(QBrush(m_bgColor));
2711     d->toolIface->themeChanged();
2712 }
2713 
2714 void EditorWindow::addAction2ContextMenu(const QString& actionName, bool addDisabled)
2715 {
2716     if (!m_contextMenu)
2717     {
2718         return;
2719     }
2720 
2721     QAction* const action = actionCollection()->action(actionName);
2722 
2723     if (action && (action->isEnabled() || addDisabled))
2724     {
2725         m_contextMenu->addAction(action);
2726     }
2727 }
2728 
2729 void EditorWindow::showSideBars(bool visible)
2730 {
2731     if (visible)
2732     {
2733         rightSideBar()->restore(QList<QWidget*>() << thumbBar(), d->fullscreenSizeBackup);
2734     }
2735     else
2736     {
2737         // See bug #166472, a simple backup()/restore() will hide non-sidebar splitter child widgets
2738         // in horizontal mode thumbbar wont be member of the splitter, it is just ignored then
2739 
2740         rightSideBar()->backup(QList<QWidget*>() << thumbBar(), &d->fullscreenSizeBackup);
2741     }
2742 }
2743 
2744 void EditorWindow::slotToggleRightSideBar()
2745 {
2746     rightSideBar()->isExpanded() ? rightSideBar()->shrink()
2747                                  : rightSideBar()->expand();
2748 }
2749 
2750 void EditorWindow::slotPreviousRightSideBarTab()
2751 {
2752     rightSideBar()->activePreviousTab();
2753 }
2754 
2755 void EditorWindow::slotNextRightSideBarTab()
2756 {
2757     rightSideBar()->activeNextTab();
2758 }
2759 
2760 void EditorWindow::showThumbBar(bool visible)
2761 {
2762     visible ? thumbBar()->restoreVisibility()
2763             : thumbBar()->hide();
2764 }
2765 
2766 bool EditorWindow::thumbbarVisibility() const
2767 {
2768     return thumbBar()->isVisible();
2769 }
2770 
2771 void EditorWindow::customizedFullScreenMode(bool set)
2772 {
2773     set ? m_canvas->setBackgroundBrush(QBrush(Qt::black))
2774         : m_canvas->setBackgroundBrush(QBrush(m_bgColor));
2775 
2776     showStatusBarAction()->setEnabled(!set);
2777     toolBarMenuAction()->setEnabled(!set);
2778     showMenuBarAction()->setEnabled(!set);
2779     m_showBarAction->setEnabled(!set);
2780 }
2781 
2782 void EditorWindow::addServicesMenuForUrl(const QUrl& url)
2783 {
2784     KService::List offers = DServiceMenu::servicesForOpenWith(QList<QUrl>() << url);
2785 
2786     qCDebug(DIGIKAM_GENERAL_LOG) << offers.count() << " services found to open " << url;
2787 
2788     if (m_servicesMenu)
2789     {
2790         delete m_servicesMenu;
2791         m_servicesMenu = nullptr;
2792     }
2793 
2794     if (m_serviceAction)
2795     {
2796         delete m_serviceAction;
2797         m_serviceAction = nullptr;
2798     }
2799 
2800     if (!offers.isEmpty())
2801     {
2802         m_servicesMenu = new QMenu(this);
2803 
2804         QAction* const serviceAction = m_servicesMenu->menuAction();
2805         serviceAction->setText(i18nc("@action", "Open With"));
2806 
2807         Q_FOREACH (const KService::Ptr& service, offers)
2808         {
2809             QString name          = service->name().replace(QLatin1Char('&'), QLatin1String("&&"));
2810             QAction* const action = m_servicesMenu->addAction(name);
2811             action->setIcon(QIcon::fromTheme(service->icon()));
2812             action->setData(service->name());
2813             d->servicesMap[name]  = service;
2814         }
2815 
2816 #ifdef HAVE_KIO
2817 
2818         m_servicesMenu->addSeparator();
2819         m_servicesMenu->addAction(i18nc("@action: open with other application", "Other..."));
2820 
2821         m_contextMenu->addAction(serviceAction);
2822 
2823         connect(m_servicesMenu, SIGNAL(triggered(QAction*)),
2824                 this, SLOT(slotOpenWith(QAction*)));
2825     }
2826     else
2827     {
2828         m_serviceAction = new QAction(i18nc("@action: open with desktop application", "Open With..."), this);
2829         m_contextMenu->addAction(m_serviceAction);
2830 
2831         connect(m_servicesMenu, SIGNAL(triggered()),
2832                 this, SLOT(slotOpenWith()));
2833 
2834 #endif // HAVE_KIO
2835 
2836     }
2837 }
2838 
2839 void EditorWindow::openWith(const QUrl& url, QAction* action)
2840 {
2841     KService::Ptr service;
2842     QString name = action ? action->data().toString() : QString();
2843 
2844 #ifdef HAVE_KIO
2845 
2846     if (name.isEmpty())
2847     {
2848         QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(QList<QUrl>() << url);
2849 
2850         if (dlg->exec() != KOpenWithDialog::Accepted)
2851         {
2852             delete dlg;
2853             return;
2854         }
2855 
2856         service = dlg->service();
2857 
2858         if (!service)
2859         {
2860             // User entered a custom command
2861 
2862             if (!dlg->text().isEmpty())
2863             {
2864                 DServiceMenu::runFiles(dlg->text(), QList<QUrl>() << url);
2865             }
2866 
2867             delete dlg;
2868             return;
2869         }
2870 
2871         delete dlg;
2872     }
2873     else
2874 
2875 #endif // HAVE_KIO
2876 
2877     {
2878         service = d->servicesMap[name];
2879     }
2880 
2881     DServiceMenu::runFiles(service, QList<QUrl>() << url);
2882 }
2883 
2884 void EditorWindow::loadTool(EditorTool* const tool)
2885 {
2886     EditorToolIface::editorToolIface()->loadTool(tool);
2887 
2888     connect(tool, SIGNAL(okClicked()),
2889             this, SLOT(slotToolDone()));
2890 
2891     connect(tool, SIGNAL(cancelClicked()),
2892             this, SLOT(slotToolDone()));
2893 }
2894 
2895 void EditorWindow::slotToolDone()
2896 {
2897     EditorToolIface::editorToolIface()->unLoadTool();
2898 }
2899 
2900 void EditorWindow::slotRotateLeftIntoQue()
2901 {
2902     m_transformQue.append(TransformType::RotateLeft);
2903 }
2904 
2905 void EditorWindow::slotRotateRightIntoQue()
2906 {
2907     m_transformQue.append(TransformType::RotateRight);
2908 }
2909 
2910 void EditorWindow::slotFlipHIntoQue()
2911 {
2912     m_transformQue.append(TransformType::FlipHorizontal);
2913 }
2914 
2915 void EditorWindow::slotFlipVIntoQue()
2916 {
2917     m_transformQue.append(TransformType::FlipVertical);
2918 }
2919 
2920 void EditorWindow::registerExtraPluginsActions(QString& dom)
2921 {
2922     DPluginLoader* const dpl      = DPluginLoader::instance();
2923     dpl->registerEditorPlugins(this);
2924     dpl->registerRawImportPlugins(this);
2925 
2926     QList<DPluginAction*> actions = dpl->pluginsActions(DPluginAction::Editor, this);
2927 
2928     Q_FOREACH (DPluginAction* const ac, actions)
2929     {
2930         actionCollection()->addActions(QList<QAction*>() << ac);
2931         actionCollection()->setDefaultShortcuts(ac, ac->shortcuts());
2932     }
2933 
2934     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_FILES_ACTIONS_ -->"),     dpl->pluginXmlSections(DPluginAction::EditorFile,     this));
2935     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_COLORS_ACTIONS_ -->"),    dpl->pluginXmlSections(DPluginAction::EditorColors,   this));
2936     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_ENHANCE_ACTIONS_ -->"),   dpl->pluginXmlSections(DPluginAction::EditorEnhance,  this));
2937 
2938     QString transformActions;
2939     transformActions.append(QLatin1String("<Action name=\"editorwindow_transform_rotateleft\" />\n"));
2940     transformActions.append(QLatin1String("<Action name=\"editorwindow_transform_rotateright\" />\n"));
2941     transformActions.append(QLatin1String("<Action name=\"editorwindow_transform_fliphoriz\" />\n"));
2942     transformActions.append(QLatin1String("<Action name=\"editorwindow_transform_flipvert\" />\n"));
2943     transformActions.append(QLatin1String("<Action name=\"editorwindow_transform_crop\" />\n"));
2944     transformActions.append(QLatin1String("<Separator/>\n"));
2945     transformActions.append(dpl->pluginXmlSections(DPluginAction::EditorTransform, this));
2946 
2947     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_TRANSFORM_ACTIONS_ -->"), transformActions);
2948     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_DECORATE_ACTIONS_ -->"),  dpl->pluginXmlSections(DPluginAction::EditorDecorate, this));
2949     dom.replace(QLatin1String("<!-- _DPLUGINS_EDITOR_FILTERS_ACTIONS_ -->"),   dpl->pluginXmlSections(DPluginAction::EditorFilters,  this));
2950 }
2951 
2952 } // namespace Digikam
2953 
2954 #include "moc_editorwindow.cpp"