diff --git a/document/kpDocumentSaveOptions.cpp b/document/kpDocumentSaveOptions.cpp index 3a249c0b..631fb094 100644 --- a/document/kpDocumentSaveOptions.cpp +++ b/document/kpDocumentSaveOptions.cpp @@ -1,622 +1,632 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT_SAVE_OPTIONS 0 #include "kpDocumentSaveOptions.h" #include "kpDefs.h" #include "pixmapfx/kpPixmapFX.h" #include #include "kpLogCategories.h" #include #include #include #include +#include //--------------------------------------------------------------------- class kpDocumentSaveOptionsPrivate { public: QString m_mimeType; int m_colorDepth{}; bool m_dither{}; int m_quality{}; }; //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions () : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = invalidMimeType (); d->m_colorDepth = invalidColorDepth (); d->m_dither = initialDither (); d->m_quality = invalidQuality (); } //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs) : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = rhs.mimeType (); d->m_colorDepth = rhs.colorDepth (); d->m_dither = rhs.dither (); d->m_quality = rhs.quality (); } //--------------------------------------------------------------------- kpDocumentSaveOptions::kpDocumentSaveOptions (const QString &mimeType, int colorDepth, bool dither, int quality) : d (new kpDocumentSaveOptionsPrivate ()) { d->m_mimeType = mimeType; d->m_colorDepth = colorDepth; d->m_dither = dither; d->m_quality = quality; } //--------------------------------------------------------------------- kpDocumentSaveOptions::~kpDocumentSaveOptions () { delete d; } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::operator== (const kpDocumentSaveOptions &rhs) const { return (mimeType () == rhs.mimeType () && colorDepth () == rhs.colorDepth () && dither () == rhs.dither () && quality () == rhs.quality ()); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::operator!= (const kpDocumentSaveOptions &rhs) const { return !(*this == rhs); } //--------------------------------------------------------------------- // public kpDocumentSaveOptions &kpDocumentSaveOptions::operator= (const kpDocumentSaveOptions &rhs) { setMimeType (rhs.mimeType ()); setColorDepth (rhs.colorDepth ()); setDither (rhs.dither ()); setQuality (rhs.quality ()); return *this; } //--------------------------------------------------------------------- // public void kpDocumentSaveOptions::printDebug (const QString &prefix) const { const QString usedPrefix = !prefix.isEmpty () ? prefix + QLatin1String (": ") : QString(); qCDebug(kpLogDocument) << usedPrefix << "mimeType=" << mimeType () << " colorDepth=" << colorDepth () << " dither=" << dither () << " quality=" << quality (); } //--------------------------------------------------------------------- // public QString kpDocumentSaveOptions::mimeType () const { return d->m_mimeType; } //--------------------------------------------------------------------- // public void kpDocumentSaveOptions::setMimeType (const QString &mimeType) { d->m_mimeType = mimeType; } //--------------------------------------------------------------------- // public static QString kpDocumentSaveOptions::invalidMimeType () { return {}; } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeIsInvalid (const QString &mimeType) { return (mimeType == invalidMimeType ()); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeIsInvalid () const { return mimeTypeIsInvalid (mimeType ()); } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::colorDepth () const { return d->m_colorDepth; } // public void kpDocumentSaveOptions::setColorDepth (int depth) { d->m_colorDepth = depth; } // public static int kpDocumentSaveOptions::invalidColorDepth () { return -1; } // public static bool kpDocumentSaveOptions::colorDepthIsInvalid (int colorDepth) { return (colorDepth != 1 && colorDepth != 8 && colorDepth != 32); } // public bool kpDocumentSaveOptions::colorDepthIsInvalid () const { return colorDepthIsInvalid (colorDepth ()); } // public bool kpDocumentSaveOptions::dither () const { return d->m_dither; } // public void kpDocumentSaveOptions::setDither (bool dither) { d->m_dither = dither; } // public static int kpDocumentSaveOptions::initialDither () { return false; // to avoid accidental double dithering } // public int kpDocumentSaveOptions::quality () const { return d->m_quality; } // public void kpDocumentSaveOptions::setQuality (int quality) { d->m_quality = quality; } // public static int kpDocumentSaveOptions::invalidQuality () { return -2; } // public static bool kpDocumentSaveOptions::qualityIsInvalid (int quality) { return (quality < -1 || quality > 100); } // public bool kpDocumentSaveOptions::qualityIsInvalid () const { return qualityIsInvalid (quality ()); } +QStringList kpDocumentSaveOptions::availableMimeTypes() +{ + QStringList mimeTypes; + for (const auto &type : QImageWriter::supportedMimeTypes()) { + mimeTypes << QString::fromLatin1(type); + } + return mimeTypes; +} + // public static QString kpDocumentSaveOptions::defaultMimeType (const KConfigGroup &config) { return config.readEntry (kpSettingForcedMimeType, QStringLiteral ("image/png")); } // public static void kpDocumentSaveOptions::saveDefaultMimeType (KConfigGroup &config, const QString &mimeType) { config.writeEntry (kpSettingForcedMimeType, mimeType); } // public static int kpDocumentSaveOptions::defaultColorDepth (const KConfigGroup &config) { int colorDepth = config.readEntry (kpSettingForcedColorDepth, -1); if (colorDepthIsInvalid (colorDepth)) { // (not screen depth, in case of transparency) colorDepth = 32; } return colorDepth; } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultColorDepth (KConfigGroup &config, int colorDepth) { config.writeEntry (kpSettingForcedColorDepth, colorDepth); } //--------------------------------------------------------------------- // public static int kpDocumentSaveOptions::defaultDither (const KConfigGroup &config) { return config.readEntry (kpSettingForcedDither, initialDither ()); } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultDither (KConfigGroup &config, bool dither) { config.writeEntry (kpSettingForcedDither, dither); } //--------------------------------------------------------------------- // public static int kpDocumentSaveOptions::defaultQuality (const KConfigGroup &config) { int val = config.readEntry (kpSettingForcedQuality, -1); return qualityIsInvalid (val) ? -1 : val; } //--------------------------------------------------------------------- // public static void kpDocumentSaveOptions::saveDefaultQuality (KConfigGroup &config, int quality) { config.writeEntry (kpSettingForcedQuality, quality); } //--------------------------------------------------------------------- // public static kpDocumentSaveOptions kpDocumentSaveOptions::defaultDocumentSaveOptions (const KConfigGroup &config) { kpDocumentSaveOptions saveOptions; saveOptions.setMimeType (defaultMimeType (config)); saveOptions.setColorDepth (defaultColorDepth (config)); saveOptions.setDither (defaultDither (config)); saveOptions.setQuality (defaultQuality (config)); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS saveOptions.printDebug ("kpDocumentSaveOptions::defaultDocumentSaveOptions()"); #endif return saveOptions; } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::saveDefaultDifferences (KConfigGroup &config, const kpDocumentSaveOptions &oldDocInfo, const kpDocumentSaveOptions &newDocInfo) { bool savedSomething = false; #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS qCDebug(kpLogDocument) << "kpDocumentSaveOptions::saveDefaultDifferences()"; oldDocInfo.printDebug ("\told"); newDocInfo.printDebug ("\tnew"); #endif if (newDocInfo.mimeType () != oldDocInfo.mimeType ()) { saveDefaultMimeType (config, newDocInfo.mimeType ()); savedSomething = true; } if (newDocInfo.colorDepth () != oldDocInfo.colorDepth ()) { saveDefaultColorDepth (config, newDocInfo.colorDepth ()); savedSomething = true; } if (newDocInfo.dither () != oldDocInfo.dither ()) { saveDefaultDither (config, newDocInfo.dither ()); savedSomething = true; } if (newDocInfo.quality () != oldDocInfo.quality ()) { saveDefaultQuality (config, newDocInfo.quality ()); savedSomething = true; } return savedSomething; } //--------------------------------------------------------------------- static QStringList mimeTypesSupportingProperty (const QString &property, const QStringList &defaultMimeTypesWithPropertyList) { QStringList mimeTypeList; KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupMimeTypeProperties); if (cfg.hasKey (property)) { mimeTypeList = cfg.readEntry (property, QStringList ()); } else { mimeTypeList = defaultMimeTypesWithPropertyList; cfg.writeEntry (property, mimeTypeList); cfg.sync (); } return mimeTypeList; } //--------------------------------------------------------------------- static bool mimeTypeSupportsProperty (const QString &mimeType, const QString &property, const QStringList &defaultMimeTypesWithPropertyList) { const auto mimeTypeList = mimeTypesSupportingProperty ( property, defaultMimeTypesWithPropertyList); return mimeTypeList.contains (mimeType); } //--------------------------------------------------------------------- // SYNC: update mime info // // Only care about writable mimetypes. // // Run: // // branches/kolourpaint/control/scripts/gen_mimetype_line.sh Write | // branches/kolourpaint/control/scripts/split_mimetype_line.pl // // in the version of kdelibs/kimgio/ (e.g. KDE 4.0) KolourPaint is shipped with, // to check for any new mimetypes to add info for. In the methods below, // you can specify this info (maximum color depth, whether it's lossy etc.). // // Update the below list and if you do change any of that info, bump up // "kpSettingsGroupMimeTypeProperties" in kpDefs.h. // // Currently, Depth and Quality settings are mutually exclusive with // Depth overriding Quality. I've currently favoured Quality with the // below mimetypes (i.e. all lossy mimetypes are only given Quality settings, // no Depth settings). // // Mimetypes done: // image/bmp // image/jpeg // image/jp2 // image/png // image/tiff // image/x-eps // image/x-pcx // image/x-portable-bitmap // image/x-portable-graymap // image/x-portable-pixmap // image/x-rgb // image/x-tga // image/x-xbitmap // image/x-xpixmap // video/x-mng [COULD NOT TEST] // // To test whether depth is configurable, write an image in the new // mimetype with all depths and read each one back. See what // kpDocument thinks the depth is when it gets QImage to read it. // public static int kpDocumentSaveOptions::mimeTypeMaximumColorDepth (const QString &mimeType) { QStringList defaultList; // SYNC: update mime info here // Grayscale actually (unenforced since depth not set to configurable) defaultList << QStringLiteral ("image/x-eps:32"); defaultList << QStringLiteral ("image/x-portable-bitmap:1"); // Grayscale actually (unenforced since depth not set to configurable) defaultList << QStringLiteral ("image/x-portable-graymap:8"); defaultList << QStringLiteral ("image/x-xbitmap:1"); const auto mimeTypeList = mimeTypesSupportingProperty ( kpSettingMimeTypeMaximumColorDepth, defaultList); const QString mimeTypeColon = mimeType + QLatin1String (":"); for (const auto & it : mimeTypeList) { if (it.startsWith (mimeTypeColon)) { int number = it.midRef (mimeTypeColon.length ()).toInt (); if (!colorDepthIsInvalid (number)) { return number; } } } return 32; } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::mimeTypeMaximumColorDepth () const { return mimeTypeMaximumColorDepth (mimeType ()); } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (const QString &mimeType) { QStringList defaultMimeTypes; // SYNC: update mime info here defaultMimeTypes << QStringLiteral ("image/png"); defaultMimeTypes << QStringLiteral ("image/bmp"); defaultMimeTypes << QStringLiteral ("image/x-pcx"); // TODO: Only 1, 24 not 8; Qt only sees 32 but "file" cmd realizes // it's either 1 or 24. defaultMimeTypes << QStringLiteral ("image/x-rgb"); // TODO: Only 8 and 24 - no 1. defaultMimeTypes << QStringLiteral ("image/x-xpixmap"); return mimeTypeSupportsProperty (mimeType, kpSettingMimeTypeHasConfigurableColorDepth, defaultMimeTypes); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth () const { return mimeTypeHasConfigurableColorDepth (mimeType ()); } //--------------------------------------------------------------------- // public static bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (const QString &mimeType) { QStringList defaultMimeTypes; // SYNC: update mime info here defaultMimeTypes << QStringLiteral ("image/jp2"); defaultMimeTypes << QStringLiteral ("image/jpeg"); defaultMimeTypes << QStringLiteral ("image/x-webp"); return mimeTypeSupportsProperty (mimeType, kpSettingMimeTypeHasConfigurableQuality, defaultMimeTypes); } //--------------------------------------------------------------------- // public bool kpDocumentSaveOptions::mimeTypeHasConfigurableQuality () const { return mimeTypeHasConfigurableQuality (mimeType ()); } //--------------------------------------------------------------------- // public int kpDocumentSaveOptions::isLossyForSaving (const QImage &image) const { auto ret = 0; if (mimeTypeMaximumColorDepth () < image.depth ()) { ret |= MimeTypeMaximumColorDepthLow; } if (mimeTypeHasConfigurableColorDepth () && !colorDepthIsInvalid () /*REFACTOR: guarantee it is valid*/ && ((colorDepth () < image.depth ()) || (colorDepth () < 32 && image.hasAlphaChannel()))) { ret |= ColorDepthLow; } if (mimeTypeHasConfigurableQuality () && !qualityIsInvalid ()) { ret |= Quality; } return ret; } //--------------------------------------------------------------------- diff --git a/document/kpDocumentSaveOptions.h b/document/kpDocumentSaveOptions.h index a28e14aa..9f8d95e7 100644 --- a/document/kpDocumentSaveOptions.h +++ b/document/kpDocumentSaveOptions.h @@ -1,150 +1,152 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef KP_DOCUMENT_SAVE_OPTIONS_H #define KP_DOCUMENT_SAVE_OPTIONS_H class QImage; class QString; +class QStringList; class KConfigGroup; class kpDocumentSaveOptions { public: kpDocumentSaveOptions (); kpDocumentSaveOptions (const kpDocumentSaveOptions &rhs); kpDocumentSaveOptions (const QString &mimeType, int colorDepth, bool dither, int quality); virtual ~kpDocumentSaveOptions (); bool operator== (const kpDocumentSaveOptions &rhs) const; bool operator!= (const kpDocumentSaveOptions &rhs) const; kpDocumentSaveOptions &operator= (const kpDocumentSaveOptions &rhs); void printDebug (const QString &prefix) const; - QString mimeType () const; void setMimeType (const QString &mimeType); static QString invalidMimeType (); static bool mimeTypeIsInvalid (const QString &mimeType); bool mimeTypeIsInvalid () const; int colorDepth () const; void setColorDepth (int depth); static int invalidColorDepth (); static bool colorDepthIsInvalid (int colorDepth); bool colorDepthIsInvalid () const; bool dither () const; void setDither (bool dither); static int initialDither (); int quality () const; void setQuality (int quality); static int invalidQuality (); static bool qualityIsInvalid (int quality); bool qualityIsInvalid () const; + static QStringList availableMimeTypes(); + // (All assume that 's group has been set) // (None of them call KConfigBase::reparseConfig() nor KConfigBase::sync()) static QString defaultMimeType (const KConfigGroup &config); static void saveDefaultMimeType (KConfigGroup &config, const QString &mimeType); static int defaultColorDepth (const KConfigGroup &config); static void saveDefaultColorDepth (KConfigGroup &config, int colorDepth); static int defaultDither (const KConfigGroup &config); static void saveDefaultDither (KConfigGroup &config, bool dither); static int defaultQuality (const KConfigGroup &config); static void saveDefaultQuality (KConfigGroup &config, int quality); static kpDocumentSaveOptions defaultDocumentSaveOptions (const KConfigGroup &config); // (returns true if it encountered a difference (and saved it to )) static bool saveDefaultDifferences (KConfigGroup &config, const kpDocumentSaveOptions &oldDocInfo, const kpDocumentSaveOptions &newDocInfo); public: // (purely for informational purposes - not enforced by this class) static int mimeTypeMaximumColorDepth (const QString &mimeType); int mimeTypeMaximumColorDepth () const; static bool mimeTypeHasConfigurableColorDepth (const QString &mimeType); bool mimeTypeHasConfigurableColorDepth () const; static bool mimeTypeHasConfigurableQuality (const QString &mimeType); bool mimeTypeHasConfigurableQuality () const; // TODO: checking for mask loss due to format e.g. BMP enum LossyType { LossLess = 0, // mimeTypeMaximumColorDepth() < .depth() MimeTypeMaximumColorDepthLow = 1, // i.e. colorDepth() < .depth() || // colorDepth() < 32 && .mask() ColorDepthLow = 2, // i.e. mimeTypeHasConfigurableQuality() Quality = 4 }; // Returns whether saving with these options will result in // loss of information. Returned value is the bitwise OR of // LossType enum possiblities. int isLossyForSaving (const QImage &image) const; private: // There is no need to maintain binary compatibility at this stage. // The d-pointer is just so that you can experiment without recompiling // the kitchen sink. class kpDocumentSaveOptionsPrivate *d; }; #endif // KP_DOCUMENT_SAVE_OPTIONS_H diff --git a/mainWindow/kpMainWindow_File.cpp b/mainWindow/kpMainWindow_File.cpp index 452de0fc..71923376 100644 --- a/mainWindow/kpMainWindow_File.cpp +++ b/mainWindow/kpMainWindow_File.cpp @@ -1,1534 +1,1523 @@ /* Copyright (c) 2003-2007 Clarence Dang Copyright (c) 2007 John Layt Copyright (c) 2007,2011,2015 Martin Koller All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "kpMainWindow.h" #include "kpMainWindowPrivate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // kdelibs4support #include #include #include #include #include #include #include #include #include #include // kdelibs4support #include "kpLogCategories.h" #include "commands/kpCommandHistory.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "commands/imagelib/kpDocumentMetaInfoCommand.h" #include "dialogs/imagelib/kpDocumentMetaInfoDialog.h" #include "widgets/kpDocumentSaveDialog.h" #include "pixmapfx/kpPixmapFX.h" #include "widgets/kpPrintDialogPage.h" #include "views/kpView.h" #include "views/manager/kpViewManager.h" #if HAVE_KSANE #include "../scan/sanedialog.h" #endif // HAVE_KSANE // private void kpMainWindow::setupFileMenuActions () { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()"; #endif KActionCollection *ac = actionCollection (); d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac); d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac); d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac); connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared); d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (kpSettingsGroupRecentFiles)); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); #endif d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac); d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac); d->actionExport = ac->addAction(QStringLiteral("file_export")); d->actionExport->setText (i18n ("E&xport...")); d->actionExport->setIcon(KDE::icon(QStringLiteral("document-export"))); connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport); d->actionScan = ac->addAction(QStringLiteral("file_scan")); d->actionScan->setText(i18n ("Scan...")); d->actionScan->setIcon(SmallIcon("scanner")); #if HAVE_KSANE connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan); #else d->actionScan->setEnabled(false); #endif // HAVE_KSANE d->actionScreenshot = ac->addAction(QStringLiteral("file_screenshot")); d->actionScreenshot->setText(i18n("Acquire Screenshot")); connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot); d->actionProperties = ac->addAction (QStringLiteral("file_properties")); d->actionProperties->setText (i18n ("Properties")); d->actionProperties->setIcon(KDE::icon(QStringLiteral("document-properties"))); connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties); //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac); d->actionReload = ac->addAction (QStringLiteral("file_revert")); d->actionReload->setText (i18n ("Reloa&d")); d->actionReload->setIcon(KDE::icon(QStringLiteral("view-refresh"))); connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload); ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ()); slotEnableReload (); d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac); d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac); d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac); d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac); d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac); d->scanDialog = nullptr; enableFileMenuDocumentActions (false); } //--------------------------------------------------------------------- // private void kpMainWindow::enableFileMenuDocumentActions (bool enable) { // d->actionNew // d->actionOpen // d->actionOpenRecent d->actionSave->setEnabled (enable); d->actionSaveAs->setEnabled (enable); d->actionExport->setEnabled (enable); // d->actionScan d->actionProperties->setEnabled (enable); // d->actionReload d->actionPrint->setEnabled (enable); d->actionPrintPreview->setEnabled (enable); d->actionMail->setEnabled (enable); d->actionClose->setEnabled (enable); // d->actionQuit->setEnabled (enable); } //--------------------------------------------------------------------- // private void kpMainWindow::addRecentURL (const QUrl &url_) { // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls // map. // // So afterwards, the URL ref, our method is given, points to an // element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)). // Accessing it would result in a crash. // // To avoid the crash, make a copy of it before calling // loadEntries() and use this copy, instead of the to-be-dangling // ref. const QUrl& url = url_; #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")"; #endif if (url.isEmpty ()) return; KSharedConfig::Ptr cfg = KSharedConfig::openConfig(); // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path cfg->reparseConfiguration (); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items (); #endif // HACK: Something might have changed interprocess. // If we could PROPAGATE: interprocess, then this wouldn't be required. d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items (); #endif d->actionOpenRecent->addUrl (url); d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles)); cfg->sync (); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items (); #endif // TODO: PROPAGATE: interprocess // TODO: Is this loop safe since a KMainWindow later along in the list, // could be closed as the code in the body almost certainly re-enters // the event loop? Problem for KDE 3 as well, I think. for (auto *kmw : KMainWindow::memberList ()) { Q_ASSERT (dynamic_cast (kmw)); auto *mw = dynamic_cast (kmw); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tmw=" << mw; #endif if (mw != this) { // WARNING: Do not use KRecentFilesAction::setItems() // - it does not work since only its superclass, // KSelectAction, implements setItems() and can't // update KRecentFilesAction's URL list. // Avoid URL memory leak in KRecentFilesAction::loadEntries(). mw->d->actionOpenRecent->clear (); mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles)); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs=" << mw->d->actionOpenRecent->items (); #endif } } } //--------------------------------------------------------------------- // private slot // TODO: Disable action if // (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty()) // as it does nothing if this is true. void kpMainWindow::slotNew () { toolEndShape (); if (d->document && !d->configOpenImagesInSameWindow) { // A document -- empty or otherwise -- is open. // Force open a new window. In contrast, open() might not open // a new window in this case. auto *win = new kpMainWindow (); win->show (); } else { open (QUrl (), true/*create an empty doc*/); } } //--------------------------------------------------------------------- // private QSize kpMainWindow::defaultDocSize () const { // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path KSharedConfig::openConfig ()->reparseConfiguration (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ()); if (docSize.isEmpty ()) { docSize = QSize (400, 300); } else { // Don't get too big or you'll thrash (or even lock up) the computer // just by opening a window docSize = QSize (qMin (2048, docSize.width ()), qMin (2048, docSize.height ())); } return docSize; } //--------------------------------------------------------------------- // private void kpMainWindow::saveDefaultDocSize (const QSize &size) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size; #endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingLastDocSize, size); cfg.sync (); } //--------------------------------------------------------------------- // private bool kpMainWindow::shouldOpen () { if (d->configOpenImagesInSameWindow) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow"; #endif // (this brings up a dialog and might save the current doc) if (!queryCloseDocument ()) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open"; #endif return false; } } return true; } //--------------------------------------------------------------------- // private void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc) { // Want new window? if (d->document && !d->document->isEmpty () && !d->configOpenImagesInSameWindow) { // Send doc to new window. auto *win = new kpMainWindow (doc); win->show (); } else { // (sets up views, doc signals) setDocument (doc); } } //--------------------------------------------------------------------- // private kpDocument *kpMainWindow::openInternal (const QUrl &url, const QSize &fallbackDocSize, bool newDocSameNameIfNotExist) { // If using OpenImagesInSameWindow mode, ask whether to close the // current document. if (!shouldOpen ()) return nullptr; // Create/open doc. auto *newDoc = new kpDocument (fallbackDocSize.width (), fallbackDocSize.height (), documentEnvironment ()); if (!newDoc->open (url, newDocSameNameIfNotExist)) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topen failed"; #endif delete newDoc; return nullptr; } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\topen OK"; #endif // Send document to current or new window. setDocumentChoosingWindow (newDoc); return newDoc; } //--------------------------------------------------------------------- // private bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist << ")"; #endif kpDocument *newDoc = openInternal (url, defaultDocSize (), newDocSameNameIfNotExist); if (newDoc) { if (newDoc->isFromURL (false/*don't bother checking exists*/)) addRecentURL (url); return true; } return false; } //--------------------------------------------------------------------- // private QList kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs) { QMimeDatabase db; QStringList filterList; QString filter; for (const auto &type : QImageReader::supportedMimeTypes()) { if ( !filter.isEmpty() ) { filter += QLatin1Char(' '); } QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type))); if ( mime.isValid() ) { QString glob = mime.globPatterns().join(QLatin1Char(' ')); filter += glob; // I want to show the mime comment AND the file glob pattern, // but to avoid that the "All Supported Files" entry shows ALL glob patterns, // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails // can hide the first pattern and I still see the second one filterList << mime.comment() + QStringLiteral(" (%1)(%2)").arg(glob).arg(glob); } } filterList.prepend(i18n("All Supported Files (%1)", filter)); QFileDialog fd(this); fd.setNameFilters(filterList); fd.setOption(QFileDialog::HideNameFilterDetails); fd.setWindowTitle(caption); if ( allowMultipleURLs ) { fd.setFileMode(QFileDialog::ExistingFiles); } if ( fd.exec() ) { return fd.selectedUrls(); } return {}; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotOpen () { toolEndShape (); const QList urls = askForOpenURLs(i18nc("@title:window", "Open Image")); for (const auto & url : urls) { open (url); } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotOpenRecent (const QUrl &url) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")"; qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items (); #endif toolEndShape (); open (url); // If the open is successful, addRecentURL() would have bubbled up the // URL in the File / Open Recent action. As a side effect, the URL is // deselected. // // If the open fails, we should deselect the URL: // // 1. for consistency // // 2. because it has not been opened. // d->actionOpenRecent->setCurrentItem (-1); } //--------------------------------------------------------------------- void kpMainWindow::slotRecentListCleared() { d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(kpSettingsGroupRecentFiles)); } //--------------------------------------------------------------------- #if HAVE_KSANE // private slot void kpMainWindow::slotScan () { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog; #endif toolEndShape (); if (!d->scanDialog) { // Create scan dialog d->scanDialog = new SaneDialog(this); // No scanning support (kdegraphics/libkscan) installed? if (!d->scanDialog) { KMessageBox::sorry (this, i18n("Failed to open scanning dialog."), i18nc("@title:window", "Scanning Failed")); return; } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog; #endif connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned); } // If using OpenImagesInSameWindow mode, ask whether to close the // current document. // // Do this after scan support is detected. Because if it's not, what // would be the point of closing the document? // // Ideally, we would do this after the user presses "Final Scan" in // the scan dialog and before the scan begins (if the user wants to // cancel the scan operation, it would be annoying to offer this choice // only after the slow scan is completed) but the KScanDialog API does // not allow this. So we settle for doing this before any // scan dialogs are shown. We don't do this between KScanDialog::setup() // and KScanDialog::exec() as it could be confusing alternating between // scanning and KolourPaint dialogs. if (!shouldOpen ()) { return; } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tcalling setup"; #endif // Bring up dialog to select scan device. // If there is no scanner, we find that this does not bring up a dialog // but still returns true. if (d->scanDialog->setup ()) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog"; #endif // Called only if scanner configured/available. // // In reality, this seems to be called even if you press "Cancel" in // the KScanDialog::setup() dialog! // // We use exec() to make sure it's modal. show() seems to work too // but better safe than sorry. d->scanDialog->exec (); } else { // Have never seen this code path execute even if "Cancel" is pressed. #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tFAIL"; #endif } } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotScanned (const QImage &image, int) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect (); #endif #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\thiding dialog"; #endif // (KScanDialog does not close itself after a scan is made) // // Close the dialog, first thing: // // 1. This means that any dialogs we bring up won't be nested on top. // // 2. We don't want to return from this method but forget to close // the dialog. So do it before anything else. d->scanDialog->hide (); // (just in case there's some drawing between slotScan() exiting and // us being called) toolEndShape (); // TODO: Maybe this code should be moved into kpdocument.cpp - // since it resembles the responsibilities of kpDocument::open(). kpDocumentSaveOptions saveOptions; kpDocumentMetaInfo metaInfo; kpDocument::getDataFromImage(image, saveOptions, metaInfo); // Create document from image and meta info. auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ()); doc->setImage (image); doc->setSaveOptions (saveOptions); doc->setMetaInfo (metaInfo); // Send document to current or new window. setDocumentChoosingWindow (doc); } #endif // HAVE_KSANE //--------------------------------------------------------------------- void kpMainWindow::slotScreenshot() { toolEndShape(); auto *dialog = new QDialog(this); auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject); auto *label = new QLabel(i18n("Snapshot Delay")); auto *seconds = new KPluralHandlingSpinBox; seconds->setRange(0, 99); seconds->setSuffix(ki18np(" second", " seconds")); seconds->setSpecialValueText(i18n("No delay")); auto *hideWindow = new QCheckBox(i18n("Hide Main Window")); hideWindow->setChecked(true); auto *vbox = new QVBoxLayout(dialog); vbox->addWidget(label); vbox->addWidget(seconds); vbox->addWidget(hideWindow); vbox->addWidget(buttons); if ( dialog->exec() == QDialog::Rejected ) { delete dialog; return; } if ( hideWindow->isChecked() ) { hide(); } // at least 1 seconds to make sure the window is hidden and the hide effect already stopped QTimer::singleShot((seconds->value() + 1) * 1000, this, &kpMainWindow::slotMakeScreenshot); delete dialog; } //--------------------------------------------------------------------- void kpMainWindow::slotMakeScreenshot() { QCoreApplication::processEvents(); QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId()); auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment()); doc->setImage(pixmap.toImage()); // Send document to current or new window. setDocumentChoosingWindow(doc); show(); // in case we hid the mainwindow, show it again } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotProperties () { toolEndShape (); kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this); if (dialog.exec () && !dialog.isNoOp ()) { commandHistory ()->addCommand ( new kpDocumentMetaInfoCommand ( i18n ("Document Properties"), dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/, commandEnvironment ())); } } //--------------------------------------------------------------------- // private slot bool kpMainWindow::save (bool localOnly) { if (d->document->url ().isEmpty () || !QImageWriter::supportedMimeTypes() .contains(d->document->saveOptions ()->mimeType().toLatin1()) || // SYNC: kpDocument::getPixmapFromFile() can't determine quality // from file so it has been set initially to an invalid value. (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () && d->document->saveOptions ()->qualityIsInvalid ()) || (localOnly && !d->document->url ().isLocalFile ())) { return saveAs (localOnly); } if (d->document->save (false/*no overwrite prompt*/, !d->document->savedAtLeastOnceBefore ()/*lossy prompt*/)) { addRecentURL (d->document->url ()); return true; } return false; } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotSave () { toolEndShape (); return save (); } //--------------------------------------------------------------------- // private QUrl kpMainWindow::askForSaveURL (const QString &caption, const QString &startURL, const kpImage &imageToBeSaved, const kpDocumentSaveOptions &startSaveOptions, const kpDocumentMetaInfo &docMetaInfo, const QString &forcedSaveOptionsGroup, bool localOnly, kpDocumentSaveOptions *chosenSaveOptions, bool isSavingForFirstTime, bool *allowOverwritePrompt, bool *allowLossyPrompt) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL; startSaveOptions.printDebug ("\tstartSaveOptions"); #endif bool reparsedConfiguration = false; // KConfig::readEntry() does not actually reread from disk, hence doesn't // realize what other processes have done e.g. Settings / Show Path // so reparseConfiguration() must be called #define SETUP_READ_CFG() \ if (!reparsedConfiguration) \ { \ KSharedConfig::openConfig ()->reparseConfiguration (); \ reparsedConfiguration = true; \ } \ \ KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); if (chosenSaveOptions) { *chosenSaveOptions = kpDocumentSaveOptions (); } if (allowOverwritePrompt) { *allowOverwritePrompt = true; // play it safe for now } if (allowLossyPrompt) { *allowLossyPrompt = true; // play it safe for now } kpDocumentSaveOptions fdSaveOptions = startSaveOptions; - QStringList mimeTypes; - for (const auto &type : QImageWriter::supportedMimeTypes()) { - mimeTypes << QString::fromLatin1(type); - } + const QStringList mimeTypes = kpDocumentSaveOptions::availableMimeTypes(); + #if DEBUG_KP_MAIN_WINDOW QStringList sortedMimeTypes = mimeTypes; sortedMimeTypes.sort (); qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes << "\tsortedMimeTypes=" << sortedMimeTypes; #endif if (mimeTypes.isEmpty ()) { qCCritical(kpLogMainWindow) << "No output mimetypes!"; return {}; } #define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \ mimeTypes.contains (fdSaveOptions.mimeType ())) if (!MIME_TYPE_IS_VALID ()) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () << " not valid, get default"; #endif SETUP_READ_CFG (); fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg)); if (!MIME_TYPE_IS_VALID ()) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType () << " not valid, get hardcoded"; #endif if (mimeTypes.contains (QStringLiteral("image/png"))) { fdSaveOptions.setMimeType (QStringLiteral("image/png")); } else if (mimeTypes.contains (QStringLiteral("image/bmp"))) { fdSaveOptions.setMimeType (QStringLiteral("image/bmp")); } else { fdSaveOptions.setMimeType (mimeTypes.first ()); } } } #undef MIME_TYPE_IS_VALID if (fdSaveOptions.colorDepthIsInvalid ()) { SETUP_READ_CFG (); fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg)); fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg)); } if (fdSaveOptions.qualityIsInvalid ()) { SETUP_READ_CFG (); fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg)); } #if DEBUG_KP_MAIN_WINDOW fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog"); #endif - auto *saveOptionsWidget = - new kpDocumentSaveDialog (imageToBeSaved, + kpDocumentSaveDialog saveOptionsWidget ( + startURL, + imageToBeSaved, fdSaveOptions, docMetaInfo, this); + saveOptionsWidget.setWindowTitle(caption); + saveOptionsWidget.setLocalOnly(localOnly); - KFileDialog fd (QUrl (startURL), QString(), this, - saveOptionsWidget); - saveOptionsWidget->setVisualParent (&fd); - fd.setWindowTitle (caption); - fd.setOperationMode (KFileDialog::Saving); - fd.setMimeFilter (mimeTypes, fdSaveOptions.mimeType ()); - if (localOnly) { - fd.setMode (KFile::File | KFile::LocalOnly); - } - - connect (&fd, &KFileDialog::filterChanged, - saveOptionsWidget, &kpDocumentSaveDialog::setMimeType); - if ( fd.exec() == QDialog::Accepted ) + if ( saveOptionsWidget.exec() == QDialog::Accepted ) { - kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions (); + kpDocumentSaveOptions newSaveOptions = saveOptionsWidget.documentSaveOptions (); #if DEBUG_KP_MAIN_WINDOW newSaveOptions.printDebug ("\tnewSaveOptions"); #endif KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup); // Save options user forced - probably want to use them in future kpDocumentSaveOptions::saveDefaultDifferences (cfg, fdSaveOptions, newSaveOptions); cfg.sync (); if (chosenSaveOptions) { *chosenSaveOptions = newSaveOptions; } bool shouldAllowOverwritePrompt = - (fd.selectedUrl () != QUrl (startURL) || + (saveOptionsWidget.selectedUrl () != QUrl (startURL) || newSaveOptions.mimeType () != startSaveOptions.mimeType ()); if (allowOverwritePrompt) { *allowOverwritePrompt = shouldAllowOverwritePrompt; #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tallowOverwritePrompt=" << *allowOverwritePrompt; #endif } if (allowLossyPrompt) { // SYNC: kpDocumentSaveOptions elements - everything except quality // (one quality setting is "just as lossy" as another so no // need to continually warn due to quality change) *allowLossyPrompt = (isSavingForFirstTime || shouldAllowOverwritePrompt || newSaveOptions.mimeType () != startSaveOptions.mimeType () || newSaveOptions.colorDepth () != startSaveOptions.colorDepth () || newSaveOptions.dither () != startSaveOptions.dither ()); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tallowLossyPrompt=" << *allowLossyPrompt; #endif } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tselectedUrl=" << fd.selectedUrl (); #endif - return fd.selectedUrl (); + return saveOptionsWidget.selectedUrl (); } return {}; #undef SETUP_READ_CFG } //--------------------------------------------------------------------- // private slot bool kpMainWindow::saveAs (bool localOnly) { kpDocumentSaveOptions chosenSaveOptions; bool allowOverwritePrompt, allowLossyPrompt; QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"), d->document->url ().url (), d->document->imageWithSelection (), *d->document->saveOptions (), *d->document->metaInfo (), kpSettingsGroupFileSaveAs, localOnly, &chosenSaveOptions, !d->document->savedAtLeastOnceBefore (), &allowOverwritePrompt, &allowLossyPrompt); if (chosenURL.isEmpty ()) { return false; } if (!d->document->saveAs (chosenURL, chosenSaveOptions, allowOverwritePrompt, allowLossyPrompt)) { return false; } addRecentURL (chosenURL); return true; } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotSaveAs () { toolEndShape (); return saveAs (); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotExport () { toolEndShape (); kpDocumentSaveOptions chosenSaveOptions; bool allowOverwritePrompt, allowLossyPrompt; QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"), d->lastExportURL.url (), d->document->imageWithSelection (), d->lastExportSaveOptions, *d->document->metaInfo (), kpSettingsGroupFileExport, false/*allow remote files*/, &chosenSaveOptions, d->exportFirstTime, &allowOverwritePrompt, &allowLossyPrompt); if (chosenURL.isEmpty ()) { return false; } if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (), chosenURL, chosenSaveOptions, *d->document->metaInfo (), allowOverwritePrompt, allowLossyPrompt, this)) { return false; } addRecentURL (chosenURL); d->lastExportURL = chosenURL; d->lastExportSaveOptions = chosenSaveOptions; d->exportFirstTime = false; return true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotEnableReload () { d->actionReload->setEnabled (d->document); } //--------------------------------------------------------------------- // private slot bool kpMainWindow::slotReload () { toolEndShape (); Q_ASSERT (d->document); QUrl oldURL = d->document->url (); if (d->document->isModified ()) { int result = KMessageBox::Cancel; if (d->document->isFromURL (false/*don't bother checking exists*/) && !oldURL.isEmpty ()) { result = KMessageBox::warningContinueCancel (this, i18n ("The document \"%1\" has been modified.\n" "Reloading will lose all changes since you last saved it.\n" "Are you sure?", d->document->prettyFilename ()), QString()/*caption*/, KGuiItem(i18n ("&Reload"))); } else { result = KMessageBox::warningContinueCancel (this, i18n ("The document \"%1\" has been modified.\n" "Reloading will lose all changes.\n" "Are you sure?", d->document->prettyFilename ()), QString()/*caption*/, KGuiItem(i18n ("&Reload"))); } if (result != KMessageBox::Continue) { return false; } } kpDocument *doc = nullptr; bool comesFromUrlOrExists = false; if (d->document->isFromURL (false/*don't bother checking exists*/)) { comesFromUrlOrExists = true; } else if (!oldURL.isEmpty()) { // 0 == only check if the file exists, don't bother with file metadata KIO::StatJob *statJob = KIO::stat(oldURL, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(statJob, this); comesFromUrlOrExists = statJob->exec(); } // If it's _supposed to_ come from a URL or it exists if (comesFromUrlOrExists) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() reloading from disk!"; #endif doc = new kpDocument (1, 1, documentEnvironment ()); if (!doc->open (oldURL)) { delete doc; doc = nullptr; return false; } addRecentURL (oldURL); } else { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() create doc"; #endif doc = new kpDocument (d->document->constructorWidth (), d->document->constructorHeight (), documentEnvironment ()); doc->setURL (oldURL, false/*not from URL*/); } setDocument (doc); return true; } // private void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer) { QUrl url = d->document->url (); if (!url.isEmpty ()) { int dot; QString fileName = url.fileName (); dot = fileName.lastIndexOf ('.'); // file.ext but not .hidden-file? if (dot > 0) { fileName.truncate (dot); } #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::sendDocumentNameToPrinter() fileName=" << fileName << " dir=" << url.path(); #endif printer->setDocName (fileName); } } //-------------------------------------------------------------------------------- void kpMainWindow::sendPreviewToPrinter(QPrinter *printer) { sendImageToPrinter(printer, false); } //-------------------------------------------------------------------------------- // private void kpMainWindow::sendImageToPrinter (QPrinter *printer, bool showPrinterSetupDialog) { // Get image to be printed. kpImage image = d->document->imageWithSelection (); // Get image DPI. auto imageDotsPerMeterX = double (d->document->metaInfo ()->dotsPerMeterX ()); auto imageDotsPerMeterY = double (d->document->metaInfo ()->dotsPerMeterY ()); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::sendImageToPrinter() image:" << " width=" << image.width () << " height=" << image.height () << " dotsPerMeterX=" << imageDotsPerMeterX << " dotsPerMeterY=" << imageDotsPerMeterY; #endif // Image DPI invalid (e.g. new image, could not read from file // or Qt3 doesn't implement DPI for JPEG)? if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0) { // Even if just one DPI dimension is invalid, mutate both DPI // dimensions as we have no information about the intended // aspect ratio anyway (and other dimension likely to be invalid). // When rendering text onto a document, the fonts are rasterised // according to the screen's DPI. // TODO: I think we should use the image's DPI. Technically // possible? // // So no matter what computer you draw text on, you get // the same pixels. // // So we must print at the screen's DPI to get the right text size. // // Unfortunately, this means that moving to a different screen DPI // affects printing. If you edited the image at a different screen // DPI than when you print, you get incorrect results. Furthermore, // this is bogus if you don't have text in your image. Worse still, // what if you have multiple screens connected to the same computer // with different DPIs? // TODO: mysteriously, someone else is setting this to 96dpi always. QPixmap arbitraryScreenElement(1, 1); const QPaintDevice *screenDevice = &arbitraryScreenElement; const auto dpiX = screenDevice->logicalDpiX (); const auto dpiY = screenDevice->logicalDpiY (); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY; #endif imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER; imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER; } // Get page size (excluding margins). // Coordinate (0,0) is the X here: // mmmmm // mX m // m m m = margin // m m // mmmmm const auto printerWidthMM = printer->widthMM (); const auto printerHeightMM = printer->heightMM (); auto dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER; auto dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER; #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM << " heightMM=" << printerHeightMM; qCDebug(kpLogMainWindow) << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY; #endif // // If image doesn't fit on page at intended DPI, change the DPI. // const auto scaleDpiX = (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) / dpiX; const auto scaleDpiY = (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) / dpiY; const auto scaleDpi = qMax (scaleDpiX, scaleDpiY); #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY << " --> scale at " << scaleDpi << " to fit?"; #endif // Need to increase resolution to fit page? if (scaleDpi > 1.0) { dpiX *= scaleDpi; dpiY *= scaleDpi; #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\t\t\tto fit page, scaled to:" << " dpiX=" << dpiX << " dpiY=" << dpiY; #endif } // Make sure DPIs are equal as that's all QPrinter::setResolution() // supports. We do this in such a way that we only ever stretch an // image, to avoid losing information. Don't antialias as the printer // will do that to translate our DPI to its physical resolution and // double-antialiasing looks bad. if (dpiX > dpiY) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX=" << dpiX; #endif kpPixmapFX::scale (&image, image.width (), qMax (1, qRound (image.height () * dpiX / dpiY)), false/*don't antialias*/); dpiY = dpiX; } else if (dpiY > dpiX) { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY=" << dpiY; #endif kpPixmapFX::scale (&image, qMax (1, qRound (image.width () * dpiY / dpiX)), image.height (), false/*don't antialias*/); dpiX = dpiY; } Q_ASSERT (dpiX == dpiY); // QPrinter::setResolution() has to be called before QPrinter::setup(). printer->setResolution (qMax (1, qRound (dpiX))); sendDocumentNameToPrinter (printer); if (showPrinterSetupDialog) { auto *optionsPage = new kpPrintDialogPage (this); optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage); QPrintDialog *printDialog = KdePrint::createPrintDialog ( printer, QList () << optionsPage, this); printDialog->setWindowTitle (i18nc ("@title:window", "Print Image")); // Display dialog. const bool wantToPrint = printDialog->exec (); if (optionsPage->printImageCenteredOnPage () != d->configPrintImageCenteredOnPage) { // Save config option even if the dialog was cancelled. d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral); cfg.writeEntry (kpSettingPrintImageCenteredOnPage, d->configPrintImageCenteredOnPage); cfg.sync (); } delete printDialog; if (!wantToPrint) { return; } } // Send image to printer. QPainter painter; painter.begin(printer); double originX = 0, originY = 0; // Center image on page? if (d->configPrintImageCenteredOnPage) { originX = (printer->width() - image.width ()) / 2; originY = (printer->height() - image.height ()) / 2; } painter.drawImage(qRound(originX), qRound(originY), image); painter.end(); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPrint () { toolEndShape (); QPrinter printer; sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotPrintPreview () { toolEndShape (); QPrintPreviewDialog printPreview(this); connect(&printPreview, &QPrintPreviewDialog::paintRequested, this, &kpMainWindow::sendPreviewToPrinter); printPreview.exec (); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotMail () { toolEndShape (); if (d->document->url ().isEmpty ()/*no name*/ || !d->document->isFromURL () || d->document->isModified ()/*needs to be saved*/) { int result = KMessageBox::questionYesNo (this, i18n ("You must save this image before sending it.\n" "Do you want to save it?"), QString(), KStandardGuiItem::save (), KStandardGuiItem::cancel ()); if (result == KMessageBox::Yes) { if (!save ()) { // save failed or aborted - don't email return; } } else { // don't want to save - don't email return; } } KToolInvocation::invokeMailer ( QString()/*to*/, QString()/*cc*/, QString()/*bcc*/, d->document->prettyFilename()/*subject*/, QString()/*body*/, QString()/*messageFile*/, QStringList(d->document->url().url())/*attachments*/); } //--------------------------------------------------------------------- // private bool kpMainWindow::queryCloseDocument () { toolEndShape (); if (!d->document || !d->document->isModified ()) { return true; // ok to close current doc } int result = KMessageBox::warningYesNoCancel (this, i18n ("The document \"%1\" has been modified.\n" "Do you want to save it?", d->document->prettyFilename ()), QString()/*caption*/, KStandardGuiItem::save (), KStandardGuiItem::discard ()); switch (result) { case KMessageBox::Yes: return slotSave (); // close only if save succeeds case KMessageBox::No: return true; // close without saving default: return false; // don't close current doc } } //--------------------------------------------------------------------- // private virtual [base KMainWindow] bool kpMainWindow::queryClose () { #if DEBUG_KP_MAIN_WINDOW qCDebug(kpLogMainWindow) << "kpMainWindow::queryClose()"; #endif toolEndShape (); if (!queryCloseDocument ()) { return false; } if (!queryCloseColors ()) { return false; } return true; } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotClose () { toolEndShape (); if (!queryCloseDocument ()) { return; } setDocument (nullptr); } //--------------------------------------------------------------------- // private slot void kpMainWindow::slotQuit () { toolEndShape (); close (); // will call queryClose() } //--------------------------------------------------------------------- diff --git a/widgets/kpDocumentSaveDialog.cpp b/widgets/kpDocumentSaveDialog.cpp index 9c241243..28c7b21b 100644 --- a/widgets/kpDocumentSaveDialog.cpp +++ b/widgets/kpDocumentSaveDialog.cpp @@ -1,754 +1,786 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET 0 #include "widgets/kpDocumentSaveDialog.h" #include "kpDefs.h" #include "document/kpDocument.h" #include "dialogs/kpDocumentSaveOptionsPreviewDialog.h" #include "pixmapfx/kpPixmapFX.h" #include "generic/widgets/kpResizeSignallingLabel.h" #include "dialogs/imagelib/transforms/kpTransformPreviewDialog.h" #include "generic/kpWidgetMapper.h" #include "kpLogCategories.h" #include #include +#include #include +#include #include #include #include #include #include #include #include #include #include #include #include kpDocumentSaveDialog::kpDocumentSaveDialog ( + const QString &url, const QImage &docPixmap, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, QWidget *parent) - : QWidget (parent), - m_visualParent (parent) + : QDialog (parent) { - init (); + init (QUrl(url)); setDocumentSaveOptions (saveOptions); setDocumentPixmap (docPixmap); setDocumentMetaInfo (metaInfo); } kpDocumentSaveDialog::kpDocumentSaveDialog ( QWidget *parent) - : QWidget (parent), - m_visualParent (parent) + : QDialog (parent) { - init (); + init (QUrl()); } // private -void kpDocumentSaveDialog::init () +void kpDocumentSaveDialog::init (const QUrl &startUrl) { + setLayout(new QVBoxLayout); + + m_fileWidget = new KFileWidget(startUrl); + m_fileWidget->setOperationMode( KFileWidget::Saving ); + m_fileWidget->setMode( KFile::Files | KFile::Directory ); + + connect(m_fileWidget, &KFileWidget::accepted, [&]() { + m_fileWidget->accept(); + + // We have to do this manually for some reason + accept(); + }); + connect (m_fileWidget, &KFileWidget::filterChanged, this, &kpDocumentSaveDialog::setMimeType); + + layout()->addWidget(m_fileWidget); + + // Normal file dialog buttons + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(m_fileWidget->okButton(), QDialogButtonBox::AcceptRole); + buttonBox->addButton(m_fileWidget->cancelButton(), QDialogButtonBox::RejectRole); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, m_fileWidget, &KFileWidget::slotOk); + layout()->addWidget(buttonBox); + m_documentPixmap = nullptr; m_previewDialog = nullptr; - m_visualParent = nullptr; m_colorDepthLabel = new QLabel (i18n ("Convert &to:"), this); m_colorDepthCombo = new QComboBox (this); m_colorDepthSpaceWidget = new QWidget (this); m_qualityLabel = new QLabel(i18n ("Quali&ty:"), this); m_qualityInput = new QSpinBox(this); // Note that we set min to 1 not 0 since "0 Quality" is a bit misleading // and 101 quality settings would be weird. So we lose 1 quality setting // according to QImage::save(). // TODO: 100 quality is also misleading since that implies perfect quality. m_qualityInput->setRange (1, 100); m_previewButton = new QPushButton (i18n ("&Preview"), this); m_previewButton->setCheckable (true); m_colorDepthLabel->setBuddy (m_colorDepthCombo); m_qualityLabel->setBuddy (m_qualityInput); + QWidget *optionsWidget = new QWidget(this); + QHBoxLayout *optionsLayout = new QHBoxLayout; + optionsLayout->addSpacerItem(new QSpacerItem(0, 30)); + optionsWidget->setLayout(optionsLayout); + optionsLayout->setContentsMargins(0, 0, 0, 0); + + optionsLayout->addWidget (m_colorDepthLabel, 0/*stretch*/, Qt::AlignLeft); + optionsLayout->addWidget (m_colorDepthCombo, 0/*stretch*/); - auto *lay = new QHBoxLayout (this); - lay->setContentsMargins(0, 0, 0, 0); + optionsLayout->addWidget (m_colorDepthSpaceWidget, 1/*stretch*/); - lay->addWidget (m_colorDepthLabel, 0/*stretch*/, Qt::AlignLeft); - lay->addWidget (m_colorDepthCombo, 0/*stretch*/); + optionsLayout->addWidget (m_qualityLabel, 0/*stretch*/, Qt::AlignLeft); + optionsLayout->addWidget (m_qualityInput, 2/*stretch*/); - lay->addWidget (m_colorDepthSpaceWidget, 1/*stretch*/); + optionsLayout->addWidget (m_previewButton, 0/*stretch*/, Qt::AlignRight); - lay->addWidget (m_qualityLabel, 0/*stretch*/, Qt::AlignLeft); - lay->addWidget (m_qualityInput, 2/*stretch*/); + // I don't like the default position it gets, so just do it old style +// buttonBox->addButton(m_previewButton, QDialogButtonBox::ApplyRole); - lay->addWidget (m_previewButton, 0/*stretch*/, Qt::AlignRight); + m_fileWidget->setCustomWidget(QString(), optionsWidget); + // To make the "automatically select extension" checkbox appear below the file type selection +// m_fileWidget->setCustomWidget(optionsWidget); connect (m_colorDepthCombo, static_cast(&QComboBox::activated), this, &kpDocumentSaveDialog::slotColorDepthSelected); connect (m_colorDepthCombo, static_cast(&QComboBox::activated), this, &kpDocumentSaveDialog::updatePreview); connect (m_qualityInput, static_cast(&QSpinBox::valueChanged), this, &kpDocumentSaveDialog::updatePreviewDelayed); connect (m_previewButton, &QPushButton::toggled, this, &kpDocumentSaveDialog::showPreview); m_updatePreviewDelay = 200/*ms*/; m_updatePreviewTimer = new QTimer (this); m_updatePreviewTimer->setSingleShot (true); connect (m_updatePreviewTimer, &QTimer::timeout, this, &kpDocumentSaveDialog::updatePreview); m_updatePreviewDialogLastRelativeGeometryTimer = new QTimer (this); connect (m_updatePreviewDialogLastRelativeGeometryTimer, &QTimer::timeout, this, &kpDocumentSaveDialog::updatePreviewDialogLastRelativeGeometry); setMode (None); slotColorDepthSelected (); } kpDocumentSaveDialog::~kpDocumentSaveDialog () { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::()"; #endif hidePreview (); delete m_documentPixmap; } - -// public -void kpDocumentSaveDialog::setVisualParent (QWidget *visualParent) +void kpDocumentSaveDialog::setLocalOnly(bool localOnly) { -#if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET - qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::setVisualParent(" - << visualParent << ")" << endl; -#endif - - m_visualParent = visualParent; + if (localOnly) { + m_fileWidget->setMode (KFile::File | KFile::LocalOnly); + } else { + m_fileWidget->setMode (KFile::File); + } } +const QUrl kpDocumentSaveDialog::selectedUrl() const +{ + return m_fileWidget->selectedUrl(); +} // protected bool kpDocumentSaveDialog::mimeTypeHasConfigurableColorDepth () const { return kpDocumentSaveOptions::mimeTypeHasConfigurableColorDepth (mimeType ()); } // protected bool kpDocumentSaveDialog::mimeTypeHasConfigurableQuality () const { return kpDocumentSaveOptions::mimeTypeHasConfigurableQuality (mimeType ()); } // public QString kpDocumentSaveDialog::mimeType () const { return m_baseDocumentSaveOptions.mimeType (); } // public slots void kpDocumentSaveDialog::setMimeType (const QString &string) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::setMimeType(" << string << ") maxColorDepth=" << kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string) << endl; #endif + m_fileWidget->setMimeFilter(kpDocumentSaveOptions::availableMimeTypes(), string); + const int newMimeTypeMaxDepth = kpDocumentSaveOptions::mimeTypeMaximumColorDepth (string); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\toldMimeType=" << mimeType () << " maxColorDepth=" << kpDocumentSaveOptions::mimeTypeMaximumColorDepth ( mimeType ()) << endl; #endif if (mimeType ().isEmpty () || kpDocumentSaveOptions::mimeTypeMaximumColorDepth (mimeType ()) != newMimeTypeMaxDepth) { m_colorDepthCombo->clear (); m_colorDepthCombo->insertItem (0, i18n ("Monochrome")); m_colorDepthCombo->insertItem (1, i18n ("Monochrome (Dithered)")); if (newMimeTypeMaxDepth >= 8) { m_colorDepthCombo->insertItem (2, i18n ("256 Color")); m_colorDepthCombo->insertItem (3, i18n ("256 Color (Dithered)")); } if (newMimeTypeMaxDepth >= 24) { m_colorDepthCombo->insertItem (4, i18n ("24-bit Color")); } if (m_colorDepthComboLastSelectedItem >= 0 && m_colorDepthComboLastSelectedItem < m_colorDepthCombo->count ()) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsetting colorDepthCombo to " << m_colorDepthComboLastSelectedItem << endl; #endif m_colorDepthCombo->setCurrentIndex (m_colorDepthComboLastSelectedItem); } else { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsetting colorDepthCombo to max item since" << " m_colorDepthComboLastSelectedItem=" << m_colorDepthComboLastSelectedItem << " out of range" << endl; #endif m_colorDepthCombo->setCurrentIndex (m_colorDepthCombo->count () - 1); } } m_baseDocumentSaveOptions.setMimeType (string); if (mimeTypeHasConfigurableColorDepth ()) { setMode (ColorDepth); } else if (mimeTypeHasConfigurableQuality ()) { setMode (Quality); } else { setMode (None); } updatePreview (); } // public int kpDocumentSaveDialog::colorDepth () const { if (mode () & ColorDepth) { // The returned values match QImage's supported depths. switch (m_colorDepthCombo->currentIndex ()) { case 0: case 1: return 1; case 2: case 3: return 8; case 4: // 24-bit is known as 32-bit with QImage. return 32; default: return kpDocumentSaveOptions::invalidColorDepth (); } } else { return m_baseDocumentSaveOptions.colorDepth (); } } // public bool kpDocumentSaveDialog::dither () const { if (mode () & ColorDepth) { return (m_colorDepthCombo->currentIndex () == 1 || m_colorDepthCombo->currentIndex () == 3); } return m_baseDocumentSaveOptions.dither (); } // protected static int kpDocumentSaveDialog::colorDepthComboItemFromColorDepthAndDither ( int depth, bool dither) { switch (depth) { case 1: if (!dither) { return 0; } return 1; case 8: if (!dither) { return 2; } return 3; case 32: return 4; default: return -1; } } // public slots void kpDocumentSaveDialog::setColorDepthDither (int newDepth, bool newDither) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::setColorDepthDither(" << "depth=" << newDepth << ",dither=" << newDither << ")" << endl; #endif m_baseDocumentSaveOptions.setColorDepth (newDepth); m_baseDocumentSaveOptions.setDither (newDither); const int comboItem = colorDepthComboItemFromColorDepthAndDither ( newDepth, newDither); // TODO: Ignoring when comboItem >= m_colorDepthCombo->count() is wrong. // This happens if this mimeType has configurable colour depth // and an incorrect maximum colour depth (less than a QImage of // this mimeType, opened by kpDocument). if (comboItem >= 0 && comboItem < m_colorDepthCombo->count ()) { m_colorDepthCombo->setCurrentIndex (comboItem); } slotColorDepthSelected (); } // protected slot void kpDocumentSaveDialog::slotColorDepthSelected () { if (mode () & ColorDepth) { m_colorDepthComboLastSelectedItem = m_colorDepthCombo->currentIndex (); } else { m_colorDepthComboLastSelectedItem = colorDepthComboItemFromColorDepthAndDither ( m_baseDocumentSaveOptions.colorDepth (), m_baseDocumentSaveOptions.dither ()); } #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::slotColorDepthSelected()" << " mode&ColorDepth=" << (mode () & ColorDepth) << " colorDepthComboLastSelectedItem=" << m_colorDepthComboLastSelectedItem << endl; #endif } // public int kpDocumentSaveDialog::quality () const { if (mode () & Quality) { return m_qualityInput->value (); } return m_baseDocumentSaveOptions.quality (); } // public void kpDocumentSaveDialog::setQuality (int newQuality) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::setQuality(" << newQuality << ")" << endl; #endif m_baseDocumentSaveOptions.setQuality (newQuality); m_qualityInput->setValue (newQuality == -1/*QImage::save() default*/ ? 75 : newQuality); } // public kpDocumentSaveOptions kpDocumentSaveDialog::documentSaveOptions () const { return kpDocumentSaveOptions (mimeType (), colorDepth (), dither (), quality ()); } // public void kpDocumentSaveDialog::setDocumentSaveOptions ( const kpDocumentSaveOptions &saveOptions) { setMimeType (saveOptions.mimeType ()); setColorDepthDither (saveOptions.colorDepth (), saveOptions.dither ()); setQuality (saveOptions.quality ()); } // public void kpDocumentSaveDialog::setDocumentPixmap (const QImage &documentPixmap) { delete m_documentPixmap; m_documentPixmap = new QImage (documentPixmap); updatePreview (); } // public void kpDocumentSaveDialog::setDocumentMetaInfo ( const kpDocumentMetaInfo &metaInfo) { m_documentMetaInfo = metaInfo; updatePreview (); } // public kpDocumentSaveDialog::Mode kpDocumentSaveDialog::mode () const { return m_mode; } // public void kpDocumentSaveDialog::setMode (Mode mode) { m_mode = mode; // If mode == None, we show still show the Color Depth widgets but disabled m_colorDepthLabel->setVisible (mode != Quality); m_colorDepthCombo->setVisible (mode != Quality); m_colorDepthSpaceWidget->setVisible (mode != Quality); m_qualityLabel->setVisible (mode == Quality); m_qualityInput->setVisible (mode == Quality); m_colorDepthLabel->setEnabled (mode == ColorDepth); m_colorDepthCombo->setEnabled (mode == ColorDepth); m_qualityLabel->setEnabled (mode == Quality); m_qualityInput->setEnabled (mode == Quality); // SYNC: HACK: When changing between color depth and quality widgets, // we change the height of "this", causing the text on the labels // to move but the first instance of the text doesn't get erased. // Qt bug. QTimer::singleShot (0, this, &kpDocumentSaveDialog::repaintLabels); } // protected slot void kpDocumentSaveDialog::repaintLabels () { if (mode () != Quality) { m_colorDepthLabel->repaint (); } if (mode () == Quality) { m_qualityLabel->repaint (); } } // protected slot void kpDocumentSaveDialog::showPreview (bool yes) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::showPreview(" << yes << ")" << " m_previewDialog=" << bool (m_previewDialog) << endl; #endif if (yes == bool (m_previewDialog)) { return; } - if (!m_visualParent) { - return; - } - if (yes) { - m_previewDialog = new kpDocumentSaveOptionsPreviewDialog( m_visualParent ); + m_previewDialog = new kpDocumentSaveOptionsPreviewDialog( this ); m_previewDialog->setObjectName( QStringLiteral( "previewSaveDialog" ) ); updatePreview (); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::finished, this, &kpDocumentSaveDialog::hidePreview); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); if (cfg.hasKey (kpSettingPreviewSaveUpdateDelay)) { m_updatePreviewDelay = cfg.readEntry (kpSettingPreviewSaveUpdateDelay, 0); } else { cfg.writeEntry (kpSettingPreviewSaveUpdateDelay, m_updatePreviewDelay); cfg.sync (); } if (m_updatePreviewDelay < 0) { m_updatePreviewDelay = 0; } #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tread cfg preview dialog update delay=" << m_updatePreviewDelay; #endif if (m_previewDialogLastRelativeGeometry.isEmpty ()) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tread cfg preview dialog last rel geometry"; #endif KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); m_previewDialogLastRelativeGeometry = cfg.readEntry ( kpSettingPreviewSaveGeometry, QRect ()); } #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tpreviewDialogLastRelativeGeometry=" << m_previewDialogLastRelativeGeometry - << " visualParent->rect()=" << m_visualParent->rect () + << " this->rect()=" << this->rect () << endl; #endif QRect relativeGeometry; if (!m_previewDialogLastRelativeGeometry.isEmpty () && - m_visualParent->rect ().intersects (m_previewDialogLastRelativeGeometry)) + this->rect ().intersects (m_previewDialogLastRelativeGeometry)) { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tok"; #endif relativeGeometry = m_previewDialogLastRelativeGeometry; } else { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\t\tinvalid"; #endif const int margin = 20; relativeGeometry = - QRect (m_visualParent->width () - + QRect (this->width () - m_previewDialog->preferredMinimumSize ().width () - margin, margin * 2, // Avoid folder combo m_previewDialog->preferredMinimumSize ().width (), m_previewDialog->preferredMinimumSize ().height ()); } const QRect globalGeometry = - kpWidgetMapper::toGlobal (m_visualParent, + kpWidgetMapper::toGlobal (this, relativeGeometry); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\trelativeGeometry=" << relativeGeometry << " globalGeometry=" << globalGeometry << endl; #endif m_previewDialog->resize (globalGeometry.size ()); m_previewDialog->move (globalGeometry.topLeft ()); m_previewDialog->show (); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tgeometry after show=" << QRect (m_previewDialog->x (), m_previewDialog->y (), m_previewDialog->width (), m_previewDialog->height ()) << endl; #endif updatePreviewDialogLastRelativeGeometry (); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::moved, this, &kpDocumentSaveDialog::updatePreviewDialogLastRelativeGeometry); connect (m_previewDialog, &kpDocumentSaveOptionsPreviewDialog::resized, this, &kpDocumentSaveDialog::updatePreviewDialogLastRelativeGeometry); m_updatePreviewDialogLastRelativeGeometryTimer->start (200/*ms*/); } else { m_updatePreviewDialogLastRelativeGeometryTimer->stop (); KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupPreviewSave); cfg.writeEntry (kpSettingPreviewSaveGeometry, m_previewDialogLastRelativeGeometry); cfg.sync (); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tsaving preview geometry " << m_previewDialogLastRelativeGeometry << " (Qt would have us believe " - << kpWidgetMapper::fromGlobal (m_visualParent, + << kpWidgetMapper::fromGlobal (this, QRect (m_previewDialog->x (), m_previewDialog->y (), m_previewDialog->width (), m_previewDialog->height ())) << ")" << endl; #endif m_previewDialog->deleteLater (); m_previewDialog = nullptr; } } // protected slot void kpDocumentSaveDialog::hidePreview () { if (m_previewButton->isChecked ()) { m_previewButton->toggle (); } } // protected slot void kpDocumentSaveDialog::updatePreviewDelayed () { // (single shot) m_updatePreviewTimer->start (m_updatePreviewDelay); } // protected slot void kpDocumentSaveDialog::updatePreview () { if (!m_previewDialog || !m_documentPixmap) { return; } m_updatePreviewTimer->stop (); QApplication::setOverrideCursor (Qt::WaitCursor); QByteArray data; QBuffer buffer (&data); buffer.open (QIODevice::WriteOnly); bool savedOK = kpDocument::savePixmapToDevice (*m_documentPixmap, &buffer, documentSaveOptions (), m_documentMetaInfo, false/*no lossy prompt*/, this); buffer.close (); QImage image; // Ignore any failed saves. // // Failed saves might literally have written half a file. The final // save (when the user clicks OK), _will_ fail so we shouldn't have a // preview even if this "half a file" is actually loadable by // QImage::loadFormData(). if (savedOK) { image.loadFromData(data); } else { // Leave as invalid. // TODO: This code path has not been well tested. // Will we trigger divide by zero errors in "m_previewDialog"? } // REFACTOR: merge with kpDocument::getPixmapFromFile() m_previewDialog->setFilePixmapAndSize (image, data.size ()); QApplication::restoreOverrideCursor (); } // protected slot void kpDocumentSaveDialog::updatePreviewDialogLastRelativeGeometry () { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "kpDocumentSaveDialog::" << "updatePreviewDialogLastRelativeGeometry()" << endl; #endif if (m_previewDialog && m_previewDialog->isVisible ()) { m_previewDialogLastRelativeGeometry = - kpWidgetMapper::fromGlobal (m_visualParent, + kpWidgetMapper::fromGlobal (this, QRect (m_previewDialog->x (), m_previewDialog->y (), m_previewDialog->width (), m_previewDialog->height ())); #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tcaching pos = " << m_previewDialogLastRelativeGeometry; #endif } else { #if DEBUG_KP_DOCUMENT_SAVE_OPTIONS_WIDGET qCDebug(kpLogWidgets) << "\tnot visible - ignoring geometry"; #endif } } diff --git a/widgets/kpDocumentSaveDialog.h b/widgets/kpDocumentSaveDialog.h index a95a99f4..02700e2e 100644 --- a/widgets/kpDocumentSaveDialog.h +++ b/widgets/kpDocumentSaveDialog.h @@ -1,156 +1,158 @@ /* Copyright (c) 2003-2007 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef kpDocumentSaveOptionsWidget_H #define kpDocumentSaveOptionsWidget_H #include #include -#include +#include #include "imagelib/kpDocumentMetaInfo.h" #include "document/kpDocumentSaveOptions.h" +class KFileWidget; class QComboBox; class QImage; class QLabel; class QTimer; class QSpinBox; class QPushButton; + class kpDocumentSaveOptionsPreviewDialog; -class kpDocumentSaveDialog : public QWidget +class kpDocumentSaveDialog : public QDialog { Q_OBJECT public: - kpDocumentSaveDialog (const QImage &docPixmap, + kpDocumentSaveDialog (const QString &url, const QImage &docPixmap, const kpDocumentSaveOptions &saveOptions, const kpDocumentMetaInfo &metaInfo, QWidget *parent); kpDocumentSaveDialog (QWidget *parent); private: - void init (); + void init (const QUrl &startUrl); public: ~kpDocumentSaveDialog () override; + void setLocalOnly(bool localOnly); - // is usually the filedialog - void setVisualParent (QWidget *visualParent); - + const QUrl selectedUrl() const; protected: bool mimeTypeHasConfigurableColorDepth () const; bool mimeTypeHasConfigurableQuality () const; public: QString mimeType () const; public slots: void setMimeType (const QString &string); public: int colorDepth () const; bool dither () const; protected: static int colorDepthComboItemFromColorDepthAndDither (int depth, bool dither); public slots: void setColorDepthDither (int depth, bool dither = kpDocumentSaveOptions::initialDither ()); protected slots: void slotColorDepthSelected (); public: int quality () const; public slots: void setQuality (int newQuality); public: kpDocumentSaveOptions documentSaveOptions () const; public slots: void setDocumentSaveOptions (const kpDocumentSaveOptions &saveOptions); public: void setDocumentPixmap (const QImage &documentPixmap); void setDocumentMetaInfo (const kpDocumentMetaInfo &metaInfo); protected: enum Mode { // (mutually exclusive) None, ColorDepth, Quality }; Mode mode () const; void setMode (Mode mode); protected slots: void repaintLabels (); protected slots: void showPreview (bool yes = true); void hidePreview (); void updatePreviewDelayed (); void updatePreview (); void updatePreviewDialogLastRelativeGeometry (); protected: QWidget *m_visualParent; + KFileWidget *m_fileWidget; Mode m_mode; QImage *m_documentPixmap; kpDocumentSaveOptions m_baseDocumentSaveOptions; kpDocumentMetaInfo m_documentMetaInfo; QLabel *m_colorDepthLabel; QComboBox *m_colorDepthCombo; int m_colorDepthComboLastSelectedItem; QWidget *m_colorDepthSpaceWidget; QLabel *m_qualityLabel; QSpinBox *m_qualityInput; QPushButton *m_previewButton; kpDocumentSaveOptionsPreviewDialog *m_previewDialog; QRect m_previewDialogLastRelativeGeometry; QTimer *m_updatePreviewTimer; int m_updatePreviewDelay; QTimer *m_updatePreviewDialogLastRelativeGeometryTimer; }; #endif // kpDocumentSaveOptionsWidget_H