You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegraphics/kolourpaint/kpmainwindow_file.cpp

1410 lines
42 KiB

/*
Copyright (c) 2003,2004,2005 Clarence Dang <dang@kde.org>
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 <tqcstring.h>
#include <tqdatastream.h>
#include <tqpaintdevicemetrics.h>
#include <tqpainter.h>
#include <tqsize.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 (TQT_TQOBJECT(this), TQT_SLOT (slotNew ()), ac);
m_actionOpen = KStdAction::open (TQT_TQOBJECT(this), TQT_SLOT (slotOpen ()), ac);
m_actionOpenRecent = KStdAction::openRecent (TQT_TQOBJECT(this), TQT_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 (TQT_TQOBJECT(this), TQT_SLOT (slotSave ()), ac);
m_actionSaveAs = KStdAction::saveAs (TQT_TQOBJECT(this), TQT_SLOT (slotSaveAs ()), ac);
m_actionExport = new KAction (i18n ("E&xport..."), 0,
TQT_TQOBJECT(this), TQT_SLOT (slotExport ()), ac, "file_export");
m_actionScan = new KAction (i18n ("Scan..."), SmallIcon ("scanner"), 0,
TQT_TQOBJECT(this), TQT_SLOT (slotScan ()), ac, "file_scan");
//m_actionRevert = KStdAction::revert (this, TQT_SLOT (slotRevert ()), ac);
m_actionReload = new KAction (i18n ("Reloa&d"), KStdAccel::reload (),
TQT_TQOBJECT(this), TQT_SLOT (slotReload ()), ac, "file_revert");
slotEnableReload ();
m_actionPrint = KStdAction::print (TQT_TQOBJECT(this), TQT_SLOT (slotPrint ()), ac);
m_actionPrintPreview = KStdAction::printPreview (TQT_TQOBJECT(this), TQT_SLOT (slotPrintPreview ()), ac);
m_actionMail = KStdAction::mail (TQT_TQOBJECT(this), TQT_SLOT (slotMail ()), ac);
m_actionSetAsWallpaperCentered = new KAction (i18n ("Set as Wa&llpaper (Centered)"), 0,
TQT_TQOBJECT(this), TQT_SLOT (slotSetAsWallpaperCentered ()), ac, "file_set_as_wallpaper_centered");
m_actionSetAsWallpaperTiled = new KAction (i18n ("Set as Wallpaper (&Tiled)"), 0,
TQT_TQOBJECT(this), TQT_SLOT (slotSetAsWallpaperTiled ()), ac, "file_set_as_wallpaper_tiled");
m_actionClose = KStdAction::close (TQT_TQOBJECT(this), TQT_SLOT (slotClose ()), ac);
m_actionQuit = KStdAction::quit (TQT_TQOBJECT(this), TQT_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 (TQPtrList <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
TQSize 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 ();
TQSize docSize = cfg->readSizeEntry (kpSettingLastDocSize);
if (docSize.isEmpty ())
{
docSize = TQSize (400, 300);
}
else
{
// Don't get too big or you'll thrash (or even lock up) the computer
// just by opening a window
docSize = TQSize (TQMIN (2048, docSize.width ()),
TQMIN (2048, docSize.height ()));
}
return docSize;
}
// private
void kpMainWindow::saveDefaultDocSize (const TQSize &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 TQSize &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 TQString &caption, const TQString &startURL,
bool allowMultipleURLs)
{
TQStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading);
#if DEBUG_KP_MAIN_WINDOW
TQStringList sortedMimeTypes = mimeTypes;
sortedMimeTypes.sort ();
kdDebug () << "kpMainWindow::askForURLs(allowMultiple="
<< allowMultipleURLs
<< ")" << endl
<< "\tmimeTypes=" << mimeTypes << endl
<< "\tsortedMimeTypes=" << sortedMimeTypes << endl;
#endif
TQString 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 () : TQString());
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 (tdegraphics/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, TQT_SIGNAL (finalImage (const TQImage &, int)),
TQT_SLOT (slotScanned (const TQImage &, 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 TQImage &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 TQImage to kpDocument's image format, gathering meta info
// from TQImage.
kpDocumentSaveOptions saveOptions;
kpDocumentMetaInfo metaInfo;
const TQPixmap 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 TQString &caption,
const TQString &startURL,
const TQPixmap &pixmapToBeSaved,
const kpDocumentSaveOptions &startSaveOptions,
const kpDocumentMetaInfo &docMetaInfo,
const TQString &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;
TQStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Writing);
#if DEBUG_KP_MAIN_WINDOW
TQStringList 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, TQString(), 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, TQT_SIGNAL (filterChanged (const TQString &)),
saveOptionsWidget, TQT_SLOT (setMimeType (const TQString &)));
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 ()),
TQString()/*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 ()),
TQString()/*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;
TQString 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.
TQPixmap 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 TQt3 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.
TQPaintDeviceMetrics 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
TQPaintDeviceMetrics 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 = TQMAX (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 TQPrinter::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 (),
TQMAX (1, tqRound (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,
TQMAX (1, tqRound (pixmap.width () * dpiY / dpiX)),
pixmap.height (),
false/*don't antialias*/);
dpiX = dpiY;
}
// ASSERT: dpiX == dpiY
// TQPrinter::setResolution() has to be called before TQPrinter::setup().
printer->setResolution (TQMAX (1, tqRound (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 TQPaintDeviceMetrics::{width,height}MM().
if (!printer->setup (this))
return;
}
// Send pixmap to printer.
TQPainter painter;
painter.begin (printer);
painter.drawPixmap (tqRound (originX), tqRound (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?"),
TQString(),
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 (
TQString()/*to*/,
TQString()/*cc*/,
TQString()/*bcc*/,
m_document->prettyFilename()/*subject*/,
TQString()/*body*/,
TQString()/*messageFile*/,
TQStringList (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*/)
{
TQString 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, TQString(),
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;
}
}
TQByteArray data;
TQDataStream dataStream (data, IO_WriteOnly);
// write path
#if DEBUG_KP_MAIN_WINDOW
kdDebug () << "kpMainWindow::setAsWallpaper() path="
<< m_document->url ().path () << endl;
#endif
dataStream << TQString (m_document->url ().path ());
// write position:
//
// SYNC: tdebase/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 tdebase
// installed so tdebase/kdesktop/KBackgroundIface.h might not be around
// to be compiled in (where user == developer :))
if (!KApplication::dcopClient ()->send ("kdesktop", "KBackgroundIface",
"setWallpaper(TQString,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()
}