summaryrefslogtreecommitdiffstats
path: root/kolourpaint/kpmainwindow_file.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kolourpaint/kpmainwindow_file.cpp')
-rw-r--r--kolourpaint/kpmainwindow_file.cpp1409
1 files changed, 1409 insertions, 0 deletions
diff --git a/kolourpaint/kpmainwindow_file.cpp b/kolourpaint/kpmainwindow_file.cpp
new file mode 100644
index 00000000..b30b323e
--- /dev/null
+++ b/kolourpaint/kpmainwindow_file.cpp
@@ -0,0 +1,1409 @@
+
+/*
+ Copyright (c) 2003,2004,2005 Clarence Dang <[email protected]>
+ 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 <qcstring.h>
+#include <qdatastream.h>
+#include <qpaintdevicemetrics.h>
+#include <qpainter.h>
+#include <qsize.h>
+
+#include <dcopclient.h>
+#include <kapplication.h>
+#include <kaction.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <kimagefilepreview.h>
+#include <kimageio.h>
+#include <kio/netaccess.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprinter.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kscan.h>
+
+#include <kpdefs.h>
+#include <kpdocument.h>
+#include <kpdocumentsaveoptionswidget.h>
+#include <kptool.h>
+#include <kpview.h>
+#include <kpviewmanager.h>
+
+
+// private
+void kpMainWindow::setupFileMenuActions ()
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::setupFileMenuActions()" << endl;
+#endif
+ KActionCollection *ac = actionCollection ();
+
+ m_actionNew = KStdAction::openNew (this, SLOT (slotNew ()), ac);
+ m_actionOpen = KStdAction::open (this, SLOT (slotOpen ()), ac);
+
+ m_actionOpenRecent = KStdAction::openRecent (this, SLOT (slotOpenRecent (const KURL &)), ac);
+ m_actionOpenRecent->loadEntries (kapp->config ());
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\trecent URLs=" << m_actionOpenRecent->items () << endl;
+#endif
+
+ m_actionSave = KStdAction::save (this, SLOT (slotSave ()), ac);
+ m_actionSaveAs = KStdAction::saveAs (this, SLOT (slotSaveAs ()), ac);
+
+ m_actionExport = new KAction (i18n ("E&xport..."), 0,
+ this, SLOT (slotExport ()), ac, "file_export");
+
+ m_actionScan = new KAction (i18n ("Scan..."), SmallIcon ("scanner"), 0,
+ this, SLOT (slotScan ()), ac, "file_scan");
+
+ //m_actionRevert = KStdAction::revert (this, SLOT (slotRevert ()), ac);
+ m_actionReload = new KAction (i18n ("Reloa&d"), KStdAccel::reload (),
+ this, SLOT (slotReload ()), ac, "file_revert");
+ slotEnableReload ();
+
+ m_actionPrint = KStdAction::print (this, SLOT (slotPrint ()), ac);
+ m_actionPrintPreview = KStdAction::printPreview (this, SLOT (slotPrintPreview ()), ac);
+
+ m_actionMail = KStdAction::mail (this, SLOT (slotMail ()), ac);
+
+ m_actionSetAsWallpaperCentered = new KAction (i18n ("Set as Wa&llpaper (Centered)"), 0,
+ this, SLOT (slotSetAsWallpaperCentered ()), ac, "file_set_as_wallpaper_centered");
+ m_actionSetAsWallpaperTiled = new KAction (i18n ("Set as Wallpaper (&Tiled)"), 0,
+ this, SLOT (slotSetAsWallpaperTiled ()), ac, "file_set_as_wallpaper_tiled");
+
+ m_actionClose = KStdAction::close (this, SLOT (slotClose ()), ac);
+ m_actionQuit = KStdAction::quit (this, SLOT (slotQuit ()), ac);
+
+ m_scanDialog = 0;
+
+ enableFileMenuDocumentActions (false);
+}
+
+// private
+void kpMainWindow::enableFileMenuDocumentActions (bool enable)
+{
+ // m_actionNew
+ // m_actionOpen
+
+ // m_actionOpenRecent
+
+ m_actionSave->setEnabled (enable);
+ m_actionSaveAs->setEnabled (enable);
+
+ m_actionExport->setEnabled (enable);
+
+ // m_actionReload
+
+ m_actionPrint->setEnabled (enable);
+ m_actionPrintPreview->setEnabled (enable);
+
+ m_actionMail->setEnabled (enable);
+
+ m_actionSetAsWallpaperCentered->setEnabled (enable);
+ m_actionSetAsWallpaperTiled->setEnabled (enable);
+
+ m_actionClose->setEnabled (enable);
+ // m_actionQuit->setEnabled (enable);
+}
+
+
+// private
+void kpMainWindow::addRecentURL (const KURL &url)
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::addRecentURL(" << url << ")" << endl;
+#endif
+ if (url.isEmpty ())
+ return;
+
+
+ KConfig *cfg = kapp->config ();
+
+ // KConfig::readEntry() does not actually reread from disk, hence doesn't
+ // realise what other processes have done e.g. Settings / Show Path
+ cfg->reparseConfiguration ();
+
+ // HACK: Something might have changed interprocess.
+ // If we could PROPAGATE: interprocess, then this wouldn't be required.
+ m_actionOpenRecent->loadEntries (cfg);
+
+ m_actionOpenRecent->addURL (url);
+
+ m_actionOpenRecent->saveEntries (cfg);
+ cfg->sync ();
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tnew recent URLs=" << m_actionOpenRecent->items () << endl;
+#endif
+
+
+ // TODO: PROPAGATE: interprocess
+ if (KMainWindow::memberList)
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\thave memberList" << endl;
+ #endif
+
+ for (QPtrList <KMainWindow>::const_iterator it = KMainWindow::memberList->begin ();
+ it != KMainWindow::memberList->end ();
+ it++)
+ {
+ kpMainWindow *mw = dynamic_cast <kpMainWindow *> (*it);
+
+ if (!mw)
+ {
+ kdError () << "kpMainWindow::addRecentURL() given fake kpMainWindow: " << (*it) << endl;
+ continue;
+ }
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\tmw=" << mw << endl;
+ #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->m_actionOpenRecent->clearURLList ();
+
+ mw->m_actionOpenRecent->loadEntries (cfg);
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\t\tcheck recent URLs="
+ << mw->m_actionOpenRecent->items () << endl;
+ #endif
+ }
+ }
+ }
+}
+
+
+// private slot
+void kpMainWindow::slotNew ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ if (m_document)
+ {
+ kpMainWindow *win = new kpMainWindow ();
+ win->show ();
+ }
+ else
+ {
+ open (KURL (), true/*create an empty doc*/);
+ }
+}
+
+
+// private
+QSize kpMainWindow::defaultDocSize () const
+{
+ // KConfig::readEntry() does not actually reread from disk, hence doesn't
+ // realise what other processes have done e.g. Settings / Show Path
+ kapp->config ()->reparseConfiguration ();
+
+ KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral);
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+ QSize docSize = cfg->readSizeEntry (kpSettingLastDocSize);
+
+ 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
+ kdDebug () << "\tCONFIG: saving Last Doc Size = " << size << endl;
+#endif
+
+ KConfigGroupSaver cfgGroupSaver (kapp->config (), kpSettingsGroupGeneral);
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+ cfg->writeEntry (kpSettingLastDocSize, size);
+ cfg->sync ();
+}
+
+
+// private
+bool kpMainWindow::shouldOpenInNewWindow () const
+{
+ return (m_document && !m_document->isEmpty ());
+}
+
+// private
+void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc)
+{
+ // need new window?
+ if (shouldOpenInNewWindow ())
+ {
+ // send doc to new window
+ kpMainWindow *win = new kpMainWindow (doc);
+ win->show ();
+ }
+ else
+ {
+ // set up views, doc signals
+ setDocument (doc);
+ }
+}
+
+
+// private
+kpDocument *kpMainWindow::openInternal (const KURL &url,
+ const QSize &fallbackDocSize,
+ bool newDocSameNameIfNotExist)
+{
+ // create doc
+ kpDocument *newDoc = new kpDocument (fallbackDocSize.width (),
+ fallbackDocSize.height (),
+ this);
+ if (!newDoc->open (url, newDocSameNameIfNotExist))
+ {
+ delete newDoc;
+ return 0;
+ }
+
+ // Send document to current or new window.
+ setDocumentChoosingWindow (newDoc);
+
+ return newDoc;
+}
+
+// private
+bool kpMainWindow::open (const KURL &url, bool newDocSameNameIfNotExist)
+{
+ kpDocument *newDoc = openInternal (url,
+ defaultDocSize (),
+ newDocSameNameIfNotExist);
+ if (newDoc)
+ {
+ if (newDoc->isFromURL (false/*don't bother checking exists*/))
+ addRecentURL (url);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+// private
+KURL::List kpMainWindow::askForOpenURLs (const QString &caption, const QString &startURL,
+ bool allowMultipleURLs)
+{
+ QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading);
+#if DEBUG_KP_MAIN_WINDOW
+ QStringList sortedMimeTypes = mimeTypes;
+ sortedMimeTypes.sort ();
+ kdDebug () << "kpMainWindow::askForURLs(allowMultiple="
+ << allowMultipleURLs
+ << ")" << endl
+ << "\tmimeTypes=" << mimeTypes << endl
+ << "\tsortedMimeTypes=" << sortedMimeTypes << endl;
+#endif
+ QString filter = mimeTypes.join (" ");
+
+ KFileDialog fd (startURL, filter, this, "fd", true/*modal*/);
+ fd.setCaption (caption);
+ fd.setOperationMode (KFileDialog::Opening);
+ if (allowMultipleURLs)
+ fd.setMode (KFile::Files);
+ fd.setPreviewWidget (new KImageFilePreview (&fd));
+
+ if (fd.exec ())
+ return fd.selectedURLs ();
+ else
+ return KURL::List ();
+}
+
+// private slot
+void kpMainWindow::slotOpen ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+
+ const KURL::List urls = askForOpenURLs (i18n ("Open Image"),
+ m_document ? m_document->url ().url () : QString::null);
+
+ for (KURL::List::const_iterator it = urls.begin ();
+ it != urls.end ();
+ it++)
+ {
+ open (*it);
+ }
+}
+
+// private slot
+void kpMainWindow::slotOpenRecent (const KURL &url)
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotOpenRecent(" << url << ")" << endl;
+ kdDebug () << "\titems=" << m_actionOpenRecent->items () << endl;
+#endif
+
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ 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.
+ //
+ m_actionOpenRecent->setCurrentItem (-1);
+}
+
+
+// private slot
+void kpMainWindow::slotScan ()
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotScan() scanDialog=" << m_scanDialog << endl;
+#endif
+
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+
+ if (!m_scanDialog)
+ {
+ // Create scan dialog by looking for plugin.
+ // [takes about 500ms on 350Mhz]
+ m_scanDialog = KScanDialog::getScanDialog (this, "scandialog", true/*modal*/);
+
+ // No scanning support (kdegraphics/libkscan) installed?
+ // [Remove $KDEDIR/share/servicetypes/kscan.desktop and
+ // $KDEDIR/share/services/scanservice.desktop to simulate this]
+ if (!m_scanDialog)
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tcould not create scan dialog" << endl;
+ #endif
+ // Instead, we could try to create the scan dialog in the ctor
+ // and just disable the action in the first place, removing
+ // the need for this dialog.
+ //
+ // But this increases startup time and is a bit risky e.g. if
+ // the scan support hangs, KolourPaint would not be able to be
+ // started at all.
+ //
+ // Also, disabling the action is bad because the scan support
+ // can be installed while KolourPaint is still running.
+ KMessageBox::sorry (this,
+ i18n ("Scanning support is not installed."),
+ i18n ("No Scanning Support"));
+ return;
+ }
+
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tcreated scanDialog=" << m_scanDialog << endl;
+ #endif
+ connect (m_scanDialog, SIGNAL (finalImage (const QImage &, int)),
+ SLOT (slotScanned (const QImage &, int)));
+ }
+
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tcalling setup" << endl;
+#endif
+ // Bring up dialog to select scan device.
+ if (m_scanDialog->setup ())
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\tOK - showing dialog" << endl;
+ #endif
+ // Called only if scanner configured/available.
+ //
+ // In reality, this seems to be called even if you press "Cancel" in
+ // the KScanDialog::setup() dialog!
+ m_scanDialog->show ();
+ }
+ else
+ {
+ // Have never seen this code path execute even if "Cancel" is pressed.
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\tFAIL" << endl;
+ #endif
+ }
+}
+
+// private slot
+void kpMainWindow::slotScanned (const QImage &image, int)
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotScanned() image.rect=" << image.rect () << endl;
+#endif
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\thiding dialog" << endl;
+#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.
+ m_scanDialog->hide ();
+
+ // (just in case there's some drawing between slotScan() exiting and
+ // us being called)
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+
+ // TODO: Maybe this code should be moved into kpdocument.cpp -
+ // since it resembles the responsibilities of kpDocument::open().
+
+ // Convert QImage to kpDocument's image format, gathering meta info
+ // from QImage.
+ kpDocumentSaveOptions saveOptions;
+ kpDocumentMetaInfo metaInfo;
+ const QPixmap pixmap = kpDocument::convertToPixmapAsLosslessAsPossible (
+ image,
+ kpMainWindow::pasteWarnAboutLossInfo (),
+ &saveOptions,
+ &metaInfo);
+
+ if (pixmap.isNull ())
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tcould not convert to pixmap" << endl;
+ #endif
+ KMessageBox::sorry (this,
+ i18n ("Cannot scan - out of graphics memory."),
+ i18n ("Cannot Scan"));
+ return;
+ }
+
+
+ // Create document from image and meta info.
+ kpDocument *doc = new kpDocument (pixmap.width (), pixmap.height (), this);
+ doc->setPixmap (pixmap);
+ doc->setSaveOptions (saveOptions);
+ doc->setMetaInfo (metaInfo);
+
+
+ // Send document to current or new window.
+ setDocumentChoosingWindow (doc);
+}
+
+
+// private slot
+bool kpMainWindow::save (bool localOnly)
+{
+ if (m_document->url ().isEmpty () ||
+ KImageIO::mimeTypes (KImageIO::Writing)
+ .findIndex (m_document->saveOptions ()->mimeType ()) < 0 ||
+ // SYNC: kpDocument::getPixmapFromFile() can't determine quality
+ // from file so it has been set initially to an invalid value.
+ (m_document->saveOptions ()->mimeTypeHasConfigurableQuality () &&
+ m_document->saveOptions ()->qualityIsInvalid ()) ||
+ (localOnly && !m_document->url ().isLocalFile ()))
+ {
+ return saveAs (localOnly);
+ }
+ else
+ {
+ if (m_document->save (false/*no overwrite prompt*/,
+ !m_document->savedAtLeastOnceBefore ()/*lossy prompt*/))
+ {
+ addRecentURL (m_document->url ());
+ return true;
+ }
+ else
+ return false;
+ }
+}
+
+// private slot
+bool kpMainWindow::slotSave ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ return save ();
+}
+
+// private
+KURL kpMainWindow::askForSaveURL (const QString &caption,
+ const QString &startURL,
+ const QPixmap &pixmapToBeSaved,
+ const kpDocumentSaveOptions &startSaveOptions,
+ const kpDocumentMetaInfo &docMetaInfo,
+ const QString &forcedSaveOptionsGroup,
+ bool localOnly,
+ kpDocumentSaveOptions *chosenSaveOptions,
+ bool isSavingForFirstTime,
+ bool *allowOverwritePrompt,
+ bool *allowLossyPrompt)
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::askForURL() startURL=" << startURL << endl;
+ startSaveOptions.printDebug ("\tstartSaveOptions");
+#endif
+
+ bool reparsedConfiguration = false;
+
+ // KConfig::readEntry() does not actually reread from disk, hence doesn't
+ // realise what other processes have done e.g. Settings / Show Path
+ // so reparseConfiguration() must be called
+#define SETUP_READ_CFG() \
+ if (!reparsedConfiguration) \
+ { \
+ kapp->config ()->reparseConfiguration (); \
+ reparsedConfiguration = true; \
+ } \
+ \
+ KConfigGroupSaver cfgGroupSaver (kapp->config (), forcedSaveOptionsGroup); \
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+
+ 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 = KImageIO::mimeTypes (KImageIO::Writing);
+#if DEBUG_KP_MAIN_WINDOW
+ QStringList sortedMimeTypes = mimeTypes;
+ sortedMimeTypes.sort ();
+ kdDebug () << "\tmimeTypes=" << mimeTypes << endl
+ << "\tsortedMimeTypes=" << sortedMimeTypes << endl;
+#endif
+ if (mimeTypes.isEmpty ())
+ {
+ kdError () << "No KImageIO output mimetypes!" << endl;
+ return KURL ();
+ }
+
+#define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () && \
+ mimeTypes.findIndex (fdSaveOptions.mimeType ()) >= 0)
+ if (!MIME_TYPE_IS_VALID ())
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tmimeType=" << fdSaveOptions.mimeType ()
+ << " not valid, get default" << endl;
+ #endif
+
+ SETUP_READ_CFG ();
+
+ fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg));
+
+
+ if (!MIME_TYPE_IS_VALID ())
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tmimeType=" << fdSaveOptions.mimeType ()
+ << " not valid, get hardcoded" << endl;
+ #endif
+ if (mimeTypes.findIndex ("image/png") > -1)
+ fdSaveOptions.setMimeType ("image/png");
+ else if (mimeTypes.findIndex ("image/x-bmp") > -1)
+ fdSaveOptions.setMimeType ("image/x-bmp");
+ else
+ fdSaveOptions.setMimeType (mimeTypes.first ());
+ }
+ }
+#undef MIME_TYPE_IN_LIST
+
+ 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
+
+ kpDocumentSaveOptionsWidget *saveOptionsWidget =
+ new kpDocumentSaveOptionsWidget (pixmapToBeSaved,
+ fdSaveOptions,
+ docMetaInfo,
+ this);
+
+ KFileDialog fd (startURL, QString::null, this, "fd", true/*modal*/,
+ saveOptionsWidget);
+ saveOptionsWidget->setVisualParent (&fd);
+ fd.setCaption (caption);
+ fd.setOperationMode (KFileDialog::Saving);
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tmimeTypes=" << mimeTypes << endl;
+#endif
+ fd.setMimeFilter (mimeTypes, fdSaveOptions.mimeType ());
+ if (localOnly)
+ fd.setMode (KFile::File | KFile::LocalOnly);
+
+ connect (&fd, SIGNAL (filterChanged (const QString &)),
+ saveOptionsWidget, SLOT (setMimeType (const QString &)));
+
+
+ if (fd.exec ())
+ {
+ kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions ();
+ #if DEBUG_KP_MAIN_WINDOW
+ newSaveOptions.printDebug ("\tnewSaveOptions");
+ #endif
+
+ KConfigGroupSaver cfgGroupSaver (kapp->config (), forcedSaveOptionsGroup);
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+ // 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 () != startURL ||
+ newSaveOptions.mimeType () != startSaveOptions.mimeType ());
+ if (allowOverwritePrompt)
+ {
+ *allowOverwritePrompt = shouldAllowOverwritePrompt;
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tallowOverwritePrompt=" << *allowOverwritePrompt << endl;
+ #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
+ kdDebug () << "\tallowLossyPrompt=" << *allowLossyPrompt << endl;
+ #endif
+ }
+
+
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tselectedURL=" << fd.selectedURL () << endl;
+ #endif
+ return fd.selectedURL ();
+ }
+ else
+ return KURL ();
+#undef SETUP_READ_CFG
+}
+
+
+// private slot
+bool kpMainWindow::saveAs (bool localOnly)
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::saveAs URL=" << m_document->url () << endl;
+#endif
+
+ kpDocumentSaveOptions chosenSaveOptions;
+ bool allowOverwritePrompt, allowLossyPrompt;
+ KURL chosenURL = askForSaveURL (i18n ("Save Image As"),
+ m_document->url ().url (),
+ m_document->pixmapWithSelection (),
+ *m_document->saveOptions (),
+ *m_document->metaInfo (),
+ kpSettingsGroupFileSaveAs,
+ localOnly,
+ &chosenSaveOptions,
+ !m_document->savedAtLeastOnceBefore (),
+ &allowOverwritePrompt,
+ &allowLossyPrompt);
+
+
+ if (chosenURL.isEmpty ())
+ return false;
+
+
+ if (!m_document->saveAs (chosenURL, chosenSaveOptions,
+ allowOverwritePrompt,
+ allowLossyPrompt))
+ {
+ return false;
+ }
+
+
+ addRecentURL (chosenURL);
+
+ return true;
+}
+
+// private slot
+bool kpMainWindow::slotSaveAs ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ return saveAs ();
+}
+
+// private slot
+bool kpMainWindow::slotExport ()
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotExport()" << endl;
+#endif
+
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+
+ kpDocumentSaveOptions chosenSaveOptions;
+ bool allowOverwritePrompt, allowLossyPrompt;
+ KURL chosenURL = askForSaveURL (i18n ("Export"),
+ m_lastExportURL.url (),
+ m_document->pixmapWithSelection (),
+ m_lastExportSaveOptions,
+ *m_document->metaInfo (),
+ kpSettingsGroupFileExport,
+ false/*allow remote files*/,
+ &chosenSaveOptions,
+ m_exportFirstTime,
+ &allowOverwritePrompt,
+ &allowLossyPrompt);
+
+
+ if (chosenURL.isEmpty ())
+ return false;
+
+
+ if (!kpDocument::savePixmapToFile (m_document->pixmapWithSelection (),
+ chosenURL,
+ chosenSaveOptions, *m_document->metaInfo (),
+ allowOverwritePrompt,
+ allowLossyPrompt,
+ this))
+ {
+ return false;
+ }
+
+
+ addRecentURL (chosenURL);
+
+
+ m_lastExportURL = chosenURL;
+ m_lastExportSaveOptions = chosenSaveOptions;
+
+ m_exportFirstTime = false;
+
+ return true;
+}
+
+
+// private slot
+void kpMainWindow::slotEnableReload ()
+{
+ m_actionReload->setEnabled (m_document);
+}
+
+// private slot
+bool kpMainWindow::slotReload ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ if (!m_document)
+ return false;
+
+
+ KURL oldURL = m_document->url ();
+
+
+ if (m_document->isModified ())
+ {
+ int result = KMessageBox::Cancel;
+
+ if (m_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?")
+ .arg (m_document->prettyFilename ()),
+ QString::null/*caption*/,
+ i18n ("&Reload"));
+ }
+ else
+ {
+ result = KMessageBox::warningContinueCancel (this,
+ i18n ("The document \"%1\" has been modified.\n"
+ "Reloading will lose all changes.\n"
+ "Are you sure?")
+ .arg (m_document->prettyFilename ()),
+ QString::null/*caption*/,
+ i18n ("&Reload"));
+ }
+
+ if (result != KMessageBox::Continue)
+ return false;
+ }
+
+
+ kpDocument *doc = 0;
+
+ // If it's _supposed to_ come from a URL or it exists
+ if (m_document->isFromURL (false/*don't bother checking exists*/) ||
+ (!oldURL.isEmpty () && KIO::NetAccess::exists (oldURL, true/*open*/, this)))
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotReload() reloading from disk!" << endl;
+ #endif
+
+ doc = new kpDocument (1, 1, this);
+ if (!doc->open (oldURL))
+ {
+ delete doc; doc = 0;
+ return false;
+ }
+
+ addRecentURL (oldURL);
+ }
+ else
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotReload() create doc" << endl;
+ #endif
+
+ doc = new kpDocument (m_document->constructorWidth (),
+ m_document->constructorHeight (),
+ this);
+ doc->setURL (oldURL, false/*not from URL*/);
+ }
+
+
+ setDocument (doc);
+
+ return true;
+}
+
+
+// private
+void kpMainWindow::sendFilenameToPrinter (KPrinter *printer)
+{
+ KURL url = m_document->url ();
+ if (!url.isEmpty ())
+ {
+ int dot;
+
+ QString fileName = url.fileName ();
+ dot = fileName.findRev ('.');
+
+ // file.ext but not .hidden-file?
+ if (dot > 0)
+ fileName.truncate (dot);
+
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::sendFilenameToPrinter() fileName="
+ << fileName
+ << " dir="
+ << url.directory ()
+ << endl;
+ #endif
+ printer->setDocName (fileName);
+ printer->setDocFileName (fileName);
+ printer->setDocDirectory (url.directory ());
+ }
+}
+
+
+static const double InchesPerMeter = 100 / 2.54;
+
+
+// TODO: GUI should allow viewing & changing of DPI.
+
+
+static bool shouldPrintImageCenteredOnPage ()
+{
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpmainwindow_file.cpp:shouldPrintImageCenteredOnPage()" << endl;
+#endif
+ bool ret;
+
+ KConfigGroupSaver cfgGroupSaver (KGlobal::config (),
+ kpSettingsGroupGeneral);
+ KConfigBase *cfg = cfgGroupSaver.config ();
+
+ if (cfg->hasKey (kpSettingPrintImageCenteredOnPage))
+ {
+ ret = cfg->readBoolEntry (kpSettingPrintImageCenteredOnPage);
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tread: " << ret << endl;
+ #endif
+ }
+ else
+ {
+ ret = true;
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tfirst time - writing default: " << ret << endl;
+#endif
+ cfg->writeEntry (kpSettingPrintImageCenteredOnPage, ret);
+ cfg->sync ();
+ }
+
+ return ret;
+}
+
+
+// private
+void kpMainWindow::sendPixmapToPrinter (KPrinter *printer,
+ bool showPrinterSetupDialog)
+{
+ // Get image to be printed.
+ QPixmap pixmap = m_document->pixmapWithSelection ();
+
+
+ // Get image DPI.
+ double pixmapDotsPerMeterX =
+ double (m_document->metaInfo ()->dotsPerMeterX ());
+ double pixmapDotsPerMeterY =
+ double (m_document->metaInfo ()->dotsPerMeterY ());
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::sendPixmapToPrinter() pixmap:"
+ << " width=" << pixmap.width ()
+ << " height=" << pixmap.height ()
+ << " dotsPerMeterX=" << pixmapDotsPerMeterX
+ << " dotsPerMeterY=" << pixmapDotsPerMeterY
+ << endl;
+#endif
+
+ // Image DPI invalid (e.g. new image, could not read from file
+ // or Qt3 doesn't implement DPI for JPEG)?
+ if (pixmapDotsPerMeterX < 1 || pixmapDotsPerMeterY < 1)
+ {
+ // 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.
+ QPaintDeviceMetrics screenMetrics (&pixmap/*screen element*/);
+ const int dpiX = screenMetrics.logicalDpiX (),
+ dpiY = screenMetrics.logicalDpiY ();
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY << endl;
+ #endif
+
+ pixmapDotsPerMeterX = dpiX * InchesPerMeter;
+ pixmapDotsPerMeterY = dpiY * InchesPerMeter;
+ }
+
+
+ // Get page size (excluding margins).
+ // Coordinate (0,0) is the X here:
+ // mmmmm
+ // mX m
+ // m m m = margin
+ // m m
+ // mmmmm
+ QPaintDeviceMetrics printerMetrics (printer);
+ const int printerWidthMM = printerMetrics.widthMM ();
+ const int printerHeightMM = printerMetrics.heightMM ();
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tprinter: widthMM=" << printerWidthMM
+ << " heightMM=" << printerHeightMM
+ << endl;
+#endif
+
+
+ double dpiX = pixmapDotsPerMeterX / InchesPerMeter;
+ double dpiY = pixmapDotsPerMeterY / InchesPerMeter;
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tpixmap: dpiX=" << dpiX << " dpiY=" << dpiY << endl;
+#endif
+
+
+ //
+ // If image doesn't fit on page at intended DPI, change the DPI.
+ //
+
+ const double scaleDpiX = (pixmap.width () / (printerWidthMM / 25.4))
+ / dpiX;
+ const double scaleDpiY = (pixmap.height () / (printerHeightMM / 25.4))
+ / dpiY;
+ const double scaleDpi = QMAX (scaleDpiX, scaleDpiY);
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY
+ << " --> scale at " << scaleDpi << " to fit?"
+ << endl;
+#endif
+
+ // Need to increase resolution to fit page?
+ if (scaleDpi > 1.0)
+ {
+ dpiX *= scaleDpi;
+ dpiY *= scaleDpi;
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\t\t\tto fit page, scaled to:"
+ << " dpiX=" << dpiX << " dpiY=" << dpiY << endl;
+ #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
+ kdDebug () << "\tdpiX > dpiY; stretching pixmap height to equalise DPIs to dpiX="
+ << dpiX << endl;
+ #endif
+ kpPixmapFX::scale (&pixmap,
+ pixmap.width (),
+ QMAX (1, qRound (pixmap.height () * dpiX / dpiY)),
+ false/*don't antialias*/);
+
+ dpiY = dpiX;
+ }
+ else if (dpiY > dpiX)
+ {
+ #if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\tdpiY > dpiX; stretching pixmap width to equalise DPIs to dpiY="
+ << dpiY << endl;
+ #endif
+ kpPixmapFX::scale (&pixmap,
+ QMAX (1, qRound (pixmap.width () * dpiY / dpiX)),
+ pixmap.height (),
+ false/*don't antialias*/);
+
+ dpiX = dpiY;
+ }
+
+
+ // ASSERT: dpiX == dpiY
+ // QPrinter::setResolution() has to be called before QPrinter::setup().
+ printer->setResolution (QMAX (1, qRound (dpiX)));
+
+
+ double originX = 0, originY = 0;
+
+ // Centre image on page?
+ if (shouldPrintImageCenteredOnPage ())
+ {
+ originX = (printerWidthMM * dpiX / 25.4 - pixmap.width ()) / 2;
+ originY = (printerHeightMM * dpiY / 25.4 - pixmap.height ()) / 2;
+ }
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "\torigin: x=" << originX << " y=" << originY << endl;
+#endif
+
+
+ sendFilenameToPrinter (printer);
+
+ if (showPrinterSetupDialog)
+ {
+ // The user can mutate margins at their own risk in this dialog.
+ // It doesn't seem to affect the size of the page as reported
+ // by QPaintDeviceMetrics::{width,height}MM().
+ if (!printer->setup (this))
+ return;
+ }
+
+
+ // Send pixmap to printer.
+ QPainter painter;
+ painter.begin (printer);
+ painter.drawPixmap (qRound (originX), qRound (originY), pixmap);
+ painter.end ();
+}
+
+
+// private slot
+void kpMainWindow::slotPrint ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ KPrinter printer;
+
+ sendPixmapToPrinter (&printer, true/*showPrinterSetupDialog*/);
+}
+
+// private slot
+void kpMainWindow::slotPrintPreview ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ // TODO: get it to reflect default printer's settings
+ KPrinter printer (false/*separate settings from ordinary printer*/);
+
+ // TODO: pass "this" as parent
+ printer.setPreviewOnly (true);
+
+ sendPixmapToPrinter (&printer, false/*don't showPrinterSetupDialog*/);
+}
+
+
+// private slot
+void kpMainWindow::slotMail ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ if (m_document->url ().isEmpty ()/*no name*/ ||
+ !m_document->isFromURL () ||
+ m_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::null,
+ KStdGuiItem::save (), KStdGuiItem::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;
+ }
+ }
+
+ kapp->invokeMailer (
+ QString::null/*to*/,
+ QString::null/*cc*/,
+ QString::null/*bcc*/,
+ m_document->prettyFilename()/*subject*/,
+ QString::null/*body*/,
+ QString::null/*messageFile*/,
+ QStringList (m_document->url ().url ())/*attachments*/);
+}
+
+
+// private
+void kpMainWindow::setAsWallpaper (bool centered)
+{
+ if (m_document->url ().isEmpty ()/*no name*/ ||
+ !m_document->url ().isLocalFile ()/*remote file*/ ||
+ !m_document->isFromURL () ||
+ m_document->isModified ()/*needs to be saved*/)
+ {
+ QString question;
+
+ if (!m_document->url ().isLocalFile ())
+ {
+ question = i18n ("Before this image can be set as the wallpaper, "
+ "you must save it as a local file.\n"
+ "Do you want to save it?");
+ }
+ else
+ {
+ question = i18n ("Before this image can be set as the wallpaper, "
+ "you must save it.\n"
+ "Do you want to save it?");
+ }
+
+ int result = KMessageBox::questionYesNo (this,
+ question, QString::null,
+ KStdGuiItem::save (), KStdGuiItem::cancel ());
+
+ if (result == KMessageBox::Yes)
+ {
+ // save() is smart enough to pop up a filedialog if it's a
+ // remote file that should be saved locally
+ if (!save (true/*localOnly*/))
+ {
+ // save failed or aborted - don't set the wallpaper
+ return;
+ }
+ }
+ else
+ {
+ // don't want to save - don't set wallpaper
+ return;
+ }
+ }
+
+
+ QByteArray data;
+ QDataStream dataStream (data, IO_WriteOnly);
+
+ // write path
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::setAsWallpaper() path="
+ << m_document->url ().path () << endl;
+#endif
+ dataStream << QString (m_document->url ().path ());
+
+ // write position:
+ //
+ // SYNC: kdebase/kcontrol/background/bgsettings.h:
+ // 1 = Centered
+ // 2 = Tiled
+ // 6 = Scaled
+ // 9 = lastWallpaperMode
+ //
+ // Why restrict the user to Centered & Tiled?
+ // Why don't we let the user choose if it should be common to all desktops?
+ // Why don't we rewrite the Background control page?
+ //
+ // Answer: This is supposed to be a quick & convenient feature.
+ //
+ // If you want more options, go to kcontrol for that kind of
+ // flexiblity. We don't want to slow down average users, who see way too
+ // many dialogs already and probably haven't even heard of "Centered Maxpect"...
+ //
+ dataStream << int (centered ? 1 : 2);
+
+
+ // I'm going to all this trouble because the user might not have kdebase
+ // installed so kdebase/kdesktop/KBackgroundIface.h might not be around
+ // to be compiled in (where user == developer :))
+ if (!KApplication::dcopClient ()->send ("kdesktop", "KBackgroundIface",
+ "setWallpaper(QString,int)", data))
+ {
+ KMessageBox::sorry (this, i18n ("Could not change wallpaper."));
+ }
+}
+
+// private slot
+void kpMainWindow::slotSetAsWallpaperCentered ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ setAsWallpaper (true/*centered*/);
+}
+
+// private slot
+void kpMainWindow::slotSetAsWallpaperTiled ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+ setAsWallpaper (false/*tiled*/);
+}
+
+
+// private slot
+void kpMainWindow::slotClose ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotClose()" << endl;
+#endif
+
+ if (!queryClose ())
+ return;
+
+ setDocument (0);
+}
+
+// private slot
+void kpMainWindow::slotQuit ()
+{
+ if (toolHasBegunShape ())
+ tool ()->endShapeInternal ();
+
+#if DEBUG_KP_MAIN_WINDOW
+ kdDebug () << "kpMainWindow::slotQuit()" << endl;
+#endif
+
+ close (); // will call queryClose()
+}
+