File indexing completed on 2024-05-12 08:19:19

0001 /*
0002    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0003    Copyright (c) 2007 John Layt <john@layt.net>
0004    Copyright (c) 2007,2011,2015 Martin Koller <kollix@aon.at>
0005    All rights reserved.
0006 
0007    Redistribution and use in source and binary forms, with or without
0008    modification, are permitted provided that the following conditions
0009    are met:
0010 
0011    1. Redistributions of source code must retain the above copyright
0012       notice, this list of conditions and the following disclaimer.
0013    2. Redistributions in binary form must reproduce the above copyright
0014       notice, this list of conditions and the following disclaimer in the
0015       documentation and/or other materials provided with the distribution.
0016 
0017    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0018    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0019    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0020    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0021    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0022    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0023    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0024    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0025    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0026    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0027 */
0028 
0029 
0030 #include "kpMainWindow.h"
0031 #include "kpMainWindowPrivate.h"
0032 
0033 #include <QAction>
0034 #include <QDialog>
0035 #include <QDialogButtonBox>
0036 #include <QFileDialog>
0037 #include <QPainter>
0038 #include <QPixmap>
0039 #include <QSize>
0040 #include <QPrinter>
0041 #include <QPrintDialog>
0042 #include <QApplication>
0043 #include <QTimer>
0044 #include <QLabel>
0045 #include <QCheckBox>
0046 #include <QVBoxLayout>
0047 #include <QImageReader>
0048 #include <QImageWriter>
0049 #include <QMimeDatabase>
0050 #include <QPrintPreviewDialog>
0051 #include <QScreen>
0052 
0053 #include <KActionCollection>
0054 #include <KEMailClientLauncherJob>
0055 #include <KSharedConfig>
0056 #include <KConfigGroup>
0057 #include <KFileCustomDialog>
0058 #include <KPluralHandlingSpinBox>
0059 #include <KMessageBox>
0060 #include <KRecentFilesAction>
0061 #include <KStandardShortcut>
0062 #include <KStandardAction>
0063 #include <KLocalizedString>
0064 #include <kwidgetsaddons_version.h>
0065 
0066 #include "kpLogCategories.h"
0067 #include "commands/kpCommandHistory.h"
0068 #include "kpDefs.h"
0069 #include "document/kpDocument.h"
0070 #include "commands/imagelib/kpDocumentMetaInfoCommand.h"
0071 #include "dialogs/imagelib/kpDocumentMetaInfoDialog.h"
0072 #include "widgets/kpDocumentSaveOptionsWidget.h"
0073 #include "pixmapfx/kpPixmapFX.h"
0074 #include "widgets/kpPrintDialogPage.h"
0075 #include "views/kpView.h"
0076 #include "views/manager/kpViewManager.h"
0077 
0078 #if HAVE_KSANE
0079 #include "../scan/sanedialog.h"
0080 #endif // HAVE_KSANE
0081 
0082 // private
0083 void kpMainWindow::setupFileMenuActions ()
0084 {
0085 #if DEBUG_KP_MAIN_WINDOW
0086     qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()";
0087 #endif
0088     KActionCollection *ac = actionCollection ();
0089 
0090     d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac);
0091     d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac);
0092 
0093     d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac);
0094     connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared);
0095     d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (QStringLiteral(kpSettingsGroupRecentFiles)));
0096 #if DEBUG_KP_MAIN_WINDOW
0097     qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
0098 #endif
0099 
0100     d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac);
0101     d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac);
0102 
0103     d->actionExport = ac->addAction(QStringLiteral("file_export"));
0104     d->actionExport->setText (i18n ("E&xport..."));
0105     d->actionExport->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
0106     d->actionExport->setToolTip(i18nc("@info:tooltip", "Export to another file"));
0107     d->actionExport->setWhatsThis(xi18nc("@info:whatsthis",
0108                                          "This saves the document with a different name, similar to <interface>Save As</interface>, "
0109                                          "but the current document is not changed.<nl/>"
0110                                          "This way you can keep the last-used save folder independent from the folder of the current document."));
0111     connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport);
0112 
0113     d->actionScan = ac->addAction(QStringLiteral("file_scan"));
0114     d->actionScan->setText(i18n ("Scan..."));
0115     d->actionScan->setIcon(QIcon::fromTheme(QStringLiteral("scanner")));
0116 #if HAVE_KSANE
0117     connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan);
0118 #else
0119     d->actionScan->setEnabled(false);
0120 #endif // HAVE_KSANE
0121 
0122     d->actionScreenshot = ac->addAction(QStringLiteral("file_screenshot"));
0123     d->actionScreenshot->setText(i18n("Acquire Screenshot"));
0124     connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot);
0125 
0126     d->actionProperties = ac->addAction (QStringLiteral("file_properties"));
0127     d->actionProperties->setText (i18n ("Properties"));
0128     d->actionProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
0129     connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties);
0130 
0131     //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac);
0132     d->actionReload = ac->addAction (QStringLiteral("file_revert"));
0133     d->actionReload->setText (i18n ("Reloa&d"));
0134     d->actionReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0135     connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload);
0136     ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ());
0137     slotEnableReload ();
0138 
0139     d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac);
0140     d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac);
0141 
0142     d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac);
0143 
0144     d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac);
0145     d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac);
0146 
0147     d->scanDialog = nullptr;
0148 
0149     enableFileMenuDocumentActions (false);
0150 }
0151 
0152 //---------------------------------------------------------------------
0153 
0154 // private
0155 void kpMainWindow::enableFileMenuDocumentActions (bool enable)
0156 {
0157     // d->actionNew
0158     // d->actionOpen
0159 
0160     // d->actionOpenRecent
0161 
0162     d->actionSave->setEnabled (enable);
0163     d->actionSaveAs->setEnabled (enable);
0164 
0165     d->actionExport->setEnabled (enable);
0166 
0167     // d->actionScan
0168 
0169     d->actionProperties->setEnabled (enable);
0170 
0171     // d->actionReload
0172 
0173     d->actionPrint->setEnabled (enable);
0174     d->actionPrintPreview->setEnabled (enable);
0175 
0176     d->actionMail->setEnabled (enable);
0177 
0178     d->actionClose->setEnabled (enable);
0179     // d->actionQuit->setEnabled (enable);
0180 }
0181 
0182 //---------------------------------------------------------------------
0183 
0184 // private
0185 void kpMainWindow::addRecentURL (const QUrl &url_)
0186 {
0187     // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls
0188     //       map.
0189     //
0190     //       So afterwards, the URL ref, our method is given, points to an
0191     //       element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)).
0192     //       Accessing it would result in a crash.
0193     //
0194     //       To avoid the crash, make a copy of it before calling
0195     //       loadEntries() and use this copy, instead of the to-be-dangling
0196     //       ref.
0197     const QUrl url = url_; // DO NOT MAKE IT A REFERENCE, THE CALL BELOW TO loadEntries DESTROYS url_
0198 
0199 #if DEBUG_KP_MAIN_WINDOW
0200     qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")";
0201 #endif
0202     if (url.isEmpty ())
0203         return;
0204 
0205 
0206     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
0207 
0208     // KConfig::readEntry() does not actually reread from disk, hence doesn't
0209     // realize what other processes have done e.g. Settings / Show Path
0210     cfg->reparseConfiguration ();
0211 
0212 #if DEBUG_KP_MAIN_WINDOW
0213     qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
0214 #endif
0215     // HACK: Something might have changed interprocess.
0216     // If we could PROPAGATE: interprocess, then this wouldn't be required.
0217     d->actionOpenRecent->loadEntries (cfg->group (QStringLiteral(kpSettingsGroupRecentFiles)));
0218 #if DEBUG_KP_MAIN_WINDOW
0219     qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items ();
0220 #endif
0221 
0222     d->actionOpenRecent->addUrl (url);
0223 
0224     d->actionOpenRecent->saveEntries (cfg->group (QStringLiteral(kpSettingsGroupRecentFiles)));
0225     cfg->sync ();
0226 
0227 #if DEBUG_KP_MAIN_WINDOW
0228     qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items ();
0229 #endif
0230 
0231 
0232     // TODO: PROPAGATE: interprocess
0233     // TODO: Is this loop safe since a KMainWindow later along in the list,
0234     //       could be closed as the code in the body almost certainly re-enters
0235     //       the event loop?  Problem for KDE 3 as well, I think.
0236     for (auto *kmw : KMainWindow::memberList ())
0237     {
0238         Q_ASSERT (dynamic_cast <kpMainWindow *> (kmw));
0239         auto *mw = dynamic_cast <kpMainWindow *> (kmw);
0240 
0241     #if DEBUG_KP_MAIN_WINDOW
0242         qCDebug(kpLogMainWindow) << "\t\tmw=" << mw;
0243     #endif
0244 
0245         if (mw != this)
0246         {
0247             // WARNING: Do not use KRecentFilesAction::setItems()
0248             //          - it does not work since only its superclass,
0249             //          KSelectAction, implements setItems() and can't
0250             //          update KRecentFilesAction's URL list.
0251 
0252             // Avoid URL memory leak in KRecentFilesAction::loadEntries().
0253             mw->d->actionOpenRecent->clear ();
0254 
0255             mw->d->actionOpenRecent->loadEntries (cfg->group (QStringLiteral(kpSettingsGroupRecentFiles)));
0256         #if DEBUG_KP_MAIN_WINDOW
0257             qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs="
0258                         << mw->d->actionOpenRecent->items ();
0259         #endif
0260         }
0261     }
0262 }
0263 
0264 //---------------------------------------------------------------------
0265 
0266 
0267 // private slot
0268 // TODO: Disable action if
0269 //       (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty())
0270 //       as it does nothing if this is true.
0271 void kpMainWindow::slotNew ()
0272 {
0273     toolEndShape ();
0274 
0275     if (d->document && !d->configOpenImagesInSameWindow)
0276     {
0277         // A document -- empty or otherwise -- is open.
0278         // Force open a new window.  In contrast, open() might not open
0279         // a new window in this case.
0280         auto *win = new kpMainWindow ();
0281         win->show ();
0282     }
0283     else
0284     {
0285         open (QUrl (), true/*create an empty doc*/);
0286     }
0287 }
0288 
0289 //---------------------------------------------------------------------
0290 
0291 
0292 // private
0293 QSize kpMainWindow::defaultDocSize () const
0294 {
0295     // KConfig::readEntry() does not actually reread from disk, hence doesn't
0296     // realize what other processes have done e.g. Settings / Show Path
0297     KSharedConfig::openConfig ()->reparseConfiguration ();
0298 
0299     KConfigGroup cfg (KSharedConfig::openConfig (), QStringLiteral(kpSettingsGroupGeneral));
0300 
0301     QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ());
0302 
0303     if (docSize.isEmpty ())
0304     {
0305         docSize = QSize (400, 300);
0306     }
0307     else
0308     {
0309         // Don't get too big or you'll thrash (or even lock up) the computer
0310         // just by opening a window
0311         docSize = QSize (qMin (2048, docSize.width ()),
0312                          qMin (2048, docSize.height ()));
0313     }
0314 
0315     return docSize;
0316 }
0317 
0318 //---------------------------------------------------------------------
0319 
0320 // private
0321 void kpMainWindow::saveDefaultDocSize (const QSize &size)
0322 {
0323 #if DEBUG_KP_MAIN_WINDOW
0324     qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size;
0325 #endif
0326 
0327     KConfigGroup cfg (KSharedConfig::openConfig (), QStringLiteral(kpSettingsGroupGeneral));
0328 
0329     cfg.writeEntry (kpSettingLastDocSize, size);
0330     cfg.sync ();
0331 }
0332 
0333 //---------------------------------------------------------------------
0334 
0335 // private
0336 bool kpMainWindow::shouldOpen ()
0337 {
0338     if (d->configOpenImagesInSameWindow)
0339     {
0340     #if DEBUG_KP_MAIN_WINDOW
0341         qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow";
0342     #endif
0343         // (this brings up a dialog and might save the current doc)
0344         if (!queryCloseDocument ())
0345         {
0346         #if DEBUG_KP_MAIN_WINDOW
0347             qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open";
0348         #endif
0349             return false;
0350         }
0351     }
0352 
0353     return true;
0354 }
0355 
0356 //---------------------------------------------------------------------
0357 
0358 // private
0359 void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc)
0360 {
0361     // Want new window?
0362     if (d->document && !d->document->isEmpty () &&
0363         !d->configOpenImagesInSameWindow)
0364     {
0365         // Send doc to new window.
0366         auto *win = new kpMainWindow (doc);
0367         win->show ();
0368     }
0369     else
0370     {
0371         // (sets up views, doc signals)
0372         setDocument (doc);
0373     }
0374 }
0375 
0376 //---------------------------------------------------------------------
0377 
0378 // private
0379 kpDocument *kpMainWindow::openInternal (const QUrl &url,
0380         const QSize &fallbackDocSize,
0381         bool newDocSameNameIfNotExist)
0382 {
0383     // If using OpenImagesInSameWindow mode, ask whether to close the
0384     // current document.
0385     if (!shouldOpen ())
0386         return nullptr;
0387 
0388     // Create/open doc.
0389     auto *newDoc = new kpDocument (fallbackDocSize.width (),
0390                                    fallbackDocSize.height (), documentEnvironment ());
0391 
0392     if (!newDoc->open (url, newDocSameNameIfNotExist))
0393     {
0394     #if DEBUG_KP_MAIN_WINDOW
0395         qCDebug(kpLogMainWindow) << "\topen failed";
0396     #endif
0397         delete newDoc;
0398         return nullptr;
0399     }
0400 
0401 #if DEBUG_KP_MAIN_WINDOW
0402     qCDebug(kpLogMainWindow) << "\topen OK";
0403 #endif
0404     // Send document to current or new window.
0405     setDocumentChoosingWindow (newDoc);
0406 
0407     return newDoc;
0408 }
0409 
0410 //---------------------------------------------------------------------
0411 
0412 // private
0413 bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist)
0414 {
0415 #if DEBUG_KP_MAIN_WINDOW
0416     qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url
0417               << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist
0418               << ")";
0419 #endif
0420 
0421     kpDocument *newDoc = openInternal (url,
0422                                        defaultDocSize (),
0423                                        newDocSameNameIfNotExist);
0424     if (newDoc)
0425     {
0426         if (newDoc->isFromExistingURL ())
0427             addRecentURL (url);
0428         return true;
0429     }
0430 
0431     return false;
0432 }
0433 
0434 //---------------------------------------------------------------------
0435 
0436 // private
0437 QList<QUrl> kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs)
0438 {
0439   QMimeDatabase db;
0440   QStringList filterList;
0441   QString filter;
0442   for (const auto &type : QImageReader::supportedMimeTypes())
0443   {
0444     if ( !filter.isEmpty() ) {
0445       filter += QLatin1Char(' ');
0446     }
0447 
0448     QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type)));
0449     if ( mime.isValid() )
0450     {
0451       QString glob = mime.globPatterns().join(QLatin1Char(' '));
0452 
0453       filter += glob;
0454 
0455       // I want to show the mime comment AND the file glob pattern,
0456       // but to avoid that the "All Supported Files" entry shows ALL glob patterns,
0457       // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails
0458       // can hide the first pattern and I still see the second one
0459       filterList << mime.comment() + QStringLiteral(" (%1)(%2)").arg(glob).arg(glob);
0460     }
0461   }
0462 
0463   filterList.prepend(i18n("All Supported Files (%1)", filter));
0464 
0465   QFileDialog fd(this);
0466   fd.setNameFilters(filterList);
0467   fd.setOption(QFileDialog::HideNameFilterDetails);
0468   fd.setWindowTitle(caption);
0469 
0470   if ( allowMultipleURLs ) {
0471     fd.setFileMode(QFileDialog::ExistingFiles);
0472   }
0473 
0474   if ( fd.exec() ) {
0475     return fd.selectedUrls();
0476   }
0477 
0478   return {};
0479 }
0480 
0481 //---------------------------------------------------------------------
0482 
0483 // private slot
0484 void kpMainWindow::slotOpen ()
0485 {
0486     toolEndShape ();
0487 
0488     const QList<QUrl> urls = askForOpenURLs(i18nc("@title:window", "Open Image"));
0489 
0490     for (const auto & url : urls)
0491     {
0492         open (url);
0493     }
0494 }
0495 
0496 //---------------------------------------------------------------------
0497 
0498 // private slot
0499 void kpMainWindow::slotOpenRecent (const QUrl &url)
0500 {
0501 #if DEBUG_KP_MAIN_WINDOW
0502     qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")";
0503     qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items ();
0504 #endif
0505 
0506     toolEndShape ();
0507 
0508     open (url);
0509 
0510     // If the open is successful, addRecentURL() would have bubbled up the
0511     // URL in the File / Open Recent action.  As a side effect, the URL is
0512     // deselected.
0513     //
0514     // If the open fails, we should deselect the URL:
0515     //
0516     // 1. for consistency
0517     //
0518     // 2. because it has not been opened.
0519     //
0520     d->actionOpenRecent->setCurrentItem (-1);
0521 }
0522 
0523 //---------------------------------------------------------------------
0524 
0525 void kpMainWindow::slotRecentListCleared()
0526 {
0527   d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(QStringLiteral(kpSettingsGroupRecentFiles)));
0528 }
0529 
0530 //---------------------------------------------------------------------
0531 
0532 #if HAVE_KSANE
0533 // private slot
0534 void kpMainWindow::slotScan ()
0535 {
0536 #if DEBUG_KP_MAIN_WINDOW
0537     qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog;
0538 #endif
0539 
0540     toolEndShape ();
0541 
0542     if (!d->scanDialog)
0543     {
0544         // Create scan dialog
0545         d->scanDialog = new SaneDialog(this);
0546 
0547         // No scanning support (kdegraphics/libkscan) installed?
0548         if (!d->scanDialog)
0549         {
0550             KMessageBox::error (this,
0551                                 i18n("Failed to open scanning dialog."),
0552                                 i18nc("@title:window", "Scanning Failed"));
0553             return;
0554         }
0555 
0556     #if DEBUG_KP_MAIN_WINDOW
0557         qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog;
0558     #endif
0559         connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned);
0560     }
0561 
0562 
0563     // If using OpenImagesInSameWindow mode, ask whether to close the
0564     // current document.
0565     //
0566     // Do this after scan support is detected.  Because if it's not, what
0567     // would be the point of closing the document?
0568     //
0569     // Ideally, we would do this after the user presses "Final Scan" in
0570     // the scan dialog and before the scan begins (if the user wants to
0571     // cancel the scan operation, it would be annoying to offer this choice
0572     // only after the slow scan is completed) but the KScanDialog API does
0573     // not allow this.  So we settle for doing this before any
0574     // scan dialogs are shown.  We don't do this between KScanDialog::setup()
0575     // and KScanDialog::exec() as it could be confusing alternating between
0576     // scanning and KolourPaint dialogs.
0577     if (!shouldOpen ()) {
0578         return;
0579     }
0580 
0581 
0582 #if DEBUG_KP_MAIN_WINDOW
0583     qCDebug(kpLogMainWindow) << "\tcalling setup";
0584 #endif
0585     // Bring up dialog to select scan device.
0586     // If there is no scanner, we find that this does not bring up a dialog
0587     // but still returns true.
0588     if (d->scanDialog->setup ())
0589     {
0590     #if DEBUG_KP_MAIN_WINDOW
0591         qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog";
0592     #endif
0593         // Called only if scanner configured/available.
0594         //
0595         // In reality, this seems to be called even if you press "Cancel" in
0596         // the KScanDialog::setup() dialog!
0597         //
0598         // We use exec() to make sure it's modal.  show() seems to work too
0599         // but better safe than sorry.
0600         d->scanDialog->exec ();
0601     }
0602     else
0603     {
0604         // Have never seen this code path execute even if "Cancel" is pressed.
0605     #if DEBUG_KP_MAIN_WINDOW
0606         qCDebug(kpLogMainWindow) << "\t\tFAIL";
0607     #endif
0608     }
0609 }
0610 
0611 //---------------------------------------------------------------------
0612 
0613 // private slot
0614 void kpMainWindow::slotScanned (const QImage &image, int)
0615 {
0616 #if DEBUG_KP_MAIN_WINDOW
0617     qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect ();
0618 #endif
0619 
0620 #if DEBUG_KP_MAIN_WINDOW
0621     qCDebug(kpLogMainWindow) << "\thiding dialog";
0622 #endif
0623     // (KScanDialog does not close itself after a scan is made)
0624     //
0625     // Close the dialog, first thing:
0626     //
0627     // 1. This means that any dialogs we bring up won't be nested on top.
0628     //
0629     // 2. We don't want to return from this method but forget to close
0630     //    the dialog.  So do it before anything else.
0631     d->scanDialog->hide ();
0632 
0633     // (just in case there's some drawing between slotScan() exiting and
0634     //  us being called)
0635     toolEndShape ();
0636 
0637 
0638     // TODO: Maybe this code should be moved into kpdocument.cpp -
0639     //       since it resembles the responsibilities of kpDocument::open().
0640 
0641     kpDocumentSaveOptions saveOptions;
0642     kpDocumentMetaInfo metaInfo;
0643 
0644     kpDocument::getDataFromImage(image, saveOptions, metaInfo);
0645 
0646     // Create document from image and meta info.
0647     auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ());
0648     doc->setImage (image);
0649     doc->setSaveOptions (saveOptions);
0650     doc->setMetaInfo (metaInfo);
0651 
0652     // Send document to current or new window.
0653     setDocumentChoosingWindow (doc);
0654 }
0655 #endif // HAVE_KSANE
0656 
0657 //---------------------------------------------------------------------
0658 
0659 void kpMainWindow::slotScreenshot()
0660 {
0661   toolEndShape();
0662 
0663   auto *dialog = new QDialog(this);
0664   auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
0665                                                    QDialogButtonBox::Cancel, dialog);
0666   connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
0667   connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
0668 
0669   auto *label = new QLabel(i18n("Snapshot Delay"));
0670   auto *seconds = new KPluralHandlingSpinBox;
0671   seconds->setRange(0, 99);
0672   seconds->setSuffix(ki18np(" second", " seconds"));
0673   seconds->setSpecialValueText(i18n("No delay"));
0674 
0675   auto *hideWindow = new QCheckBox(i18n("Hide Main Window"));
0676   hideWindow->setChecked(true);
0677 
0678   auto *vbox = new QVBoxLayout(dialog);
0679   vbox->addWidget(label);
0680   vbox->addWidget(seconds);
0681   vbox->addWidget(hideWindow);
0682   vbox->addWidget(buttons);
0683 
0684   if ( dialog->exec() == QDialog::Rejected )
0685   {
0686     delete dialog;
0687     return;
0688   }
0689 
0690   if ( hideWindow->isChecked() ) {
0691     hide();
0692   }
0693 
0694   // at least 1 seconds to make sure the window is hidden and the hide effect already stopped
0695   QTimer::singleShot((seconds->value() + 1) * 1000, this, &kpMainWindow::slotMakeScreenshot);
0696 
0697   delete dialog;
0698 }
0699 
0700 //---------------------------------------------------------------------
0701 
0702 void kpMainWindow::slotMakeScreenshot()
0703 {
0704   QCoreApplication::processEvents();
0705   QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(0 /* entire screen*/);
0706 
0707   auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment());
0708   doc->setImage(pixmap.toImage());
0709 
0710   // Send document to current or new window.
0711   setDocumentChoosingWindow(doc);
0712 
0713   show();  // in case we hid the mainwindow, show it again
0714 }
0715 
0716 //---------------------------------------------------------------------
0717 
0718 // private slot
0719 void kpMainWindow::slotProperties ()
0720 {
0721     toolEndShape ();
0722 
0723     kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this);
0724 
0725     if (dialog.exec () && !dialog.isNoOp ())
0726     {
0727         commandHistory ()->addCommand (
0728             new kpDocumentMetaInfoCommand (
0729                 i18n ("Document Properties"),
0730                 dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/,
0731                 commandEnvironment ()));
0732     }
0733 }
0734 
0735 //---------------------------------------------------------------------
0736 
0737 // private slot
0738 bool kpMainWindow::save (bool localOnly)
0739 {
0740     if (d->document->url ().isEmpty () ||
0741         !QImageWriter::supportedMimeTypes()
0742             .contains(d->document->saveOptions ()->mimeType().toLatin1()) ||
0743         // SYNC: kpDocument::getPixmapFromFile() can't determine quality
0744         //       from file so it has been set initially to an invalid value.
0745         (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () &&
0746             d->document->saveOptions ()->qualityIsInvalid ()) ||
0747         (localOnly && !d->document->url ().isLocalFile ()))
0748     {
0749         return saveAs (localOnly);
0750     }
0751 
0752     if (d->document->save (!d->document->savedAtLeastOnceBefore ()/*lossy prompt*/))
0753     {
0754         addRecentURL (d->document->url ());
0755         return true;
0756     }
0757 
0758     return false;
0759 }
0760 
0761 //---------------------------------------------------------------------
0762 
0763 // private slot
0764 bool kpMainWindow::slotSave ()
0765 {
0766     toolEndShape ();
0767 
0768     return save ();
0769 }
0770 
0771 //---------------------------------------------------------------------
0772 
0773 // private
0774 QUrl kpMainWindow::askForSaveURL (const QString &caption,
0775                                   const QString &startURL,
0776                                   const kpImage &imageToBeSaved,
0777                                   const kpDocumentSaveOptions &startSaveOptions,
0778                                   const kpDocumentMetaInfo &docMetaInfo,
0779                                   const QString &forcedSaveOptionsGroup,
0780                                   bool localOnly,
0781                                   kpDocumentSaveOptions *chosenSaveOptions,
0782                                   bool isSavingForFirstTime,
0783                                   bool *allowLossyPrompt)
0784 {
0785 #if DEBUG_KP_MAIN_WINDOW
0786     qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL;
0787     startSaveOptions.printDebug ("\tstartSaveOptions");
0788 #endif
0789 
0790     bool reparsedConfiguration = false;
0791 
0792     // KConfig::readEntry() does not actually reread from disk, hence doesn't
0793     // realize what other processes have done e.g. Settings / Show Path
0794     // so reparseConfiguration() must be called
0795 #define SETUP_READ_CFG()                                                             \
0796     if (!reparsedConfiguration)                                                      \
0797     {                                                                                \
0798         KSharedConfig::openConfig ()->reparseConfiguration ();                       \
0799         reparsedConfiguration = true;                                                \
0800     }                                                                                \
0801                                                                                      \
0802     KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup);
0803 
0804 
0805     if (chosenSaveOptions) {
0806         *chosenSaveOptions = kpDocumentSaveOptions ();
0807     }
0808 
0809     if (allowLossyPrompt) {
0810         *allowLossyPrompt = true;  // play it safe for now
0811     }
0812 
0813 
0814     kpDocumentSaveOptions fdSaveOptions = startSaveOptions;
0815 
0816     QStringList mimeTypes;
0817     for (const auto &type : QImageWriter::supportedMimeTypes()) {
0818       mimeTypes << QString::fromLatin1(type);
0819     }
0820 #if DEBUG_KP_MAIN_WINDOW
0821     QStringList sortedMimeTypes = mimeTypes;
0822     sortedMimeTypes.sort ();
0823     qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes
0824                << "\tsortedMimeTypes=" << sortedMimeTypes;
0825 #endif
0826     if (mimeTypes.isEmpty ())
0827     {
0828         qCCritical(kpLogMainWindow) << "No output mimetypes!";
0829         return {};
0830     }
0831 
0832 #define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () &&                 \
0833                               mimeTypes.contains (fdSaveOptions.mimeType ()))
0834     if (!MIME_TYPE_IS_VALID ())
0835     {
0836     #if DEBUG_KP_MAIN_WINDOW
0837         qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType ()
0838                    << " not valid, get default";
0839     #endif
0840 
0841         SETUP_READ_CFG ();
0842 
0843         fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg));
0844 
0845 
0846         if (!MIME_TYPE_IS_VALID ())
0847         {
0848         #if DEBUG_KP_MAIN_WINDOW
0849             qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType ()
0850                        << " not valid, get hardcoded";
0851         #endif
0852             if (mimeTypes.contains(QLatin1String("image/png"))) {
0853                 fdSaveOptions.setMimeType (QStringLiteral("image/png"));
0854             }
0855             else if (mimeTypes.contains(QLatin1String("image/bmp"))) {
0856                 fdSaveOptions.setMimeType (QStringLiteral("image/bmp"));
0857             }
0858             else {
0859                 fdSaveOptions.setMimeType (mimeTypes.first ());
0860             }
0861         }
0862     }
0863 #undef MIME_TYPE_IS_VALID
0864 
0865     if (fdSaveOptions.colorDepthIsInvalid ())
0866     {
0867         SETUP_READ_CFG ();
0868 
0869         fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg));
0870         fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg));
0871     }
0872 
0873     if (fdSaveOptions.qualityIsInvalid ())
0874     {
0875         SETUP_READ_CFG ();
0876 
0877         fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg));
0878     }
0879 #if DEBUG_KP_MAIN_WINDOW
0880     fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog");
0881 #endif
0882 
0883     auto *saveOptionsWidget =
0884         new kpDocumentSaveOptionsWidget (imageToBeSaved,
0885             fdSaveOptions,
0886             docMetaInfo,
0887             this);
0888 
0889     KFileCustomDialog fd (QUrl (startURL), this);
0890     fd.setOperationMode (KFileWidget::Saving);
0891     fd.setWindowTitle (caption);
0892     fd.setCustomWidget (saveOptionsWidget);
0893     KFileWidget *fw = fd.fileWidget();
0894     fw->setConfirmOverwrite (true);
0895 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0896     fw->setMimeFilter (mimeTypes, fdSaveOptions.mimeType ());
0897 #else
0898     fw->setFilters (KFileFilter::fromMimeTypes(mimeTypes), KFileFilter::fromMimeType(fdSaveOptions.mimeType ()));
0899 #endif
0900     if (localOnly) {
0901         fw->setMode (KFile::File | KFile::LocalOnly);
0902     }
0903 
0904     saveOptionsWidget->setVisualParent (&fd);
0905 
0906     connect (fw, &KFileWidget::filterChanged,
0907 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0908              saveOptionsWidget, &kpDocumentSaveOptionsWidget::setMimeType);
0909 #else
0910              saveOptionsWidget, [saveOptionsWidget](const KFileFilter &filter) {
0911                  saveOptionsWidget->setMimeType(filter.mimePatterns().first());
0912             });
0913 #endif
0914 
0915     if ( fd.exec() == QDialog::Accepted )
0916     {
0917         kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions ();
0918     #if DEBUG_KP_MAIN_WINDOW
0919         newSaveOptions.printDebug ("\tnewSaveOptions");
0920     #endif
0921 
0922         KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup);
0923 
0924         // Save options user forced - probably want to use them in future
0925         kpDocumentSaveOptions::saveDefaultDifferences (cfg,
0926             fdSaveOptions, newSaveOptions);
0927         cfg.sync ();
0928 
0929 
0930         if (chosenSaveOptions) {
0931             *chosenSaveOptions = newSaveOptions;
0932         }
0933 
0934         const QList<QUrl> selectedUrls = fw->selectedUrls ();
0935         if (selectedUrls.isEmpty()) { // shouldn't happen
0936             return {};
0937         }
0938         const QUrl selectedUrl = selectedUrls.at(0);
0939 
0940         if (allowLossyPrompt)
0941         {
0942             // SYNC: kpDocumentSaveOptions elements - everything except quality
0943             //       (one quality setting is "just as lossy" as another so no
0944             //        need to continually warn due to quality change)
0945             *allowLossyPrompt =
0946                 (isSavingForFirstTime ||
0947                  selectedUrl != QUrl (startURL) ||
0948                  newSaveOptions.mimeType () != startSaveOptions.mimeType () ||
0949                  newSaveOptions.colorDepth () != startSaveOptions.colorDepth () ||
0950                  newSaveOptions.dither () != startSaveOptions.dither ());
0951         #if DEBUG_KP_MAIN_WINDOW
0952             qCDebug(kpLogMainWindow) << "\tallowLossyPrompt=" << *allowLossyPrompt;
0953         #endif
0954         }
0955 
0956 
0957     #if DEBUG_KP_MAIN_WINDOW
0958         qCDebug(kpLogMainWindow) << "\tselectedUrl=" << selectedUrl;
0959     #endif
0960         return selectedUrl;
0961     }
0962 
0963     return {};
0964 #undef SETUP_READ_CFG
0965 }
0966 
0967 //---------------------------------------------------------------------
0968 
0969 // private slot
0970 bool kpMainWindow::saveAs (bool localOnly)
0971 {
0972     kpDocumentSaveOptions chosenSaveOptions;
0973     bool allowLossyPrompt;
0974     QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"),
0975                                     d->document->url ().url (),
0976                                     d->document->imageWithSelection (),
0977                                     *d->document->saveOptions (),
0978                                     *d->document->metaInfo (),
0979                                     QLatin1String(kpSettingsGroupFileSaveAs),
0980                                     localOnly,
0981                                     &chosenSaveOptions,
0982                                     !d->document->savedAtLeastOnceBefore (),
0983                                     &allowLossyPrompt);
0984 
0985 
0986     if (chosenURL.isEmpty ()) {
0987         return false;
0988     }
0989 
0990 
0991     if (!d->document->saveAs (chosenURL, chosenSaveOptions,
0992                              allowLossyPrompt))
0993     {
0994         return false;
0995     }
0996 
0997 
0998     addRecentURL (chosenURL);
0999 
1000     return true;
1001 }
1002 
1003 //---------------------------------------------------------------------
1004 
1005 // private slot
1006 bool kpMainWindow::slotSaveAs ()
1007 {
1008     toolEndShape ();
1009 
1010     return saveAs ();
1011 }
1012 
1013 //---------------------------------------------------------------------
1014 
1015 // private slot
1016 bool kpMainWindow::slotExport ()
1017 {
1018     toolEndShape ();
1019 
1020     kpDocumentSaveOptions chosenSaveOptions;
1021     bool allowLossyPrompt;
1022     QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"),
1023                                     d->lastExportURL.url (),
1024                                     d->document->imageWithSelection (),
1025                                     d->lastExportSaveOptions,
1026                                     *d->document->metaInfo (),
1027                                     QLatin1String(kpSettingsGroupFileExport),
1028                                     false/*allow remote files*/,
1029                                     &chosenSaveOptions,
1030                                     d->exportFirstTime,
1031                                     &allowLossyPrompt);
1032 
1033 
1034     if (chosenURL.isEmpty ()) {
1035         return false;
1036     }
1037 
1038     if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (),
1039                                        chosenURL,
1040                                        chosenSaveOptions, *d->document->metaInfo (),
1041                                        allowLossyPrompt,
1042                                        this))
1043     {
1044         return false;
1045     }
1046 
1047 
1048     addRecentURL (chosenURL);
1049 
1050     d->lastExportURL = chosenURL;
1051     d->lastExportSaveOptions = chosenSaveOptions;
1052 
1053     d->exportFirstTime = false;
1054 
1055     return true;
1056 }
1057 
1058 //---------------------------------------------------------------------
1059 
1060 // private slot
1061 void kpMainWindow::slotEnableReload ()
1062 {
1063     d->actionReload->setEnabled (d->document);
1064 }
1065 
1066 //---------------------------------------------------------------------
1067 
1068 // private slot
1069 bool kpMainWindow::slotReload ()
1070 {
1071     toolEndShape ();
1072 
1073     Q_ASSERT (d->document);
1074 
1075 
1076     QUrl oldURL = d->document->url ();
1077 
1078 
1079     if (d->document->isModified ())
1080     {
1081         int result = KMessageBox::Cancel;
1082 
1083         if (d->document->isFromExistingURL () && !oldURL.isEmpty ())
1084         {
1085             result = KMessageBox::warningContinueCancel (this,
1086                          i18n ("The document \"%1\" has been modified.\n"
1087                                "Reloading will lose all changes since you last saved it.\n"
1088                                "Are you sure?",
1089                                d->document->prettyFilename ()),
1090                          QString()/*caption*/,
1091                          KGuiItem(i18n ("&Reload")));
1092         }
1093         else
1094         {
1095             result = KMessageBox::warningContinueCancel (this,
1096                          i18n ("The document \"%1\" has been modified.\n"
1097                                "Reloading will lose all changes.\n"
1098                                "Are you sure?",
1099                                d->document->prettyFilename ()),
1100                          QString()/*caption*/,
1101                          KGuiItem(i18n ("&Reload")));
1102         }
1103 
1104         if (result != KMessageBox::Continue) {
1105             return false;
1106         }
1107     }
1108 
1109 
1110     kpDocument *doc = nullptr;
1111 
1112     // If it's _supposed to_ come from an existing URL or it actually exists
1113     if (d->document->isFromExistingURL () ||
1114         d->document->urlExists (oldURL))
1115     {
1116     #if DEBUG_KP_MAIN_WINDOW
1117         qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() reloading from disk!";
1118     #endif
1119 
1120         doc = new kpDocument (1, 1, documentEnvironment ());
1121         if (!doc->open (oldURL))
1122         {
1123             delete doc; doc = nullptr;
1124             return false;
1125         }
1126 
1127         addRecentURL (oldURL);
1128     }
1129     else
1130     {
1131     #if DEBUG_KP_MAIN_WINDOW
1132         qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() create doc";
1133     #endif
1134 
1135         doc = new kpDocument (d->document->constructorWidth (),
1136                               d->document->constructorHeight (),
1137                               documentEnvironment ());
1138         doc->setURL (oldURL, false/*not from URL*/);
1139     }
1140 
1141 
1142     setDocument (doc);
1143 
1144     return true;
1145 }
1146 
1147 
1148 // private
1149 void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer)
1150 {
1151     QUrl url = d->document->url ();
1152     if (!url.isEmpty ())
1153     {
1154         int dot;
1155 
1156         QString fileName = url.fileName ();
1157         dot = fileName.lastIndexOf (QLatin1Char('.'));
1158 
1159         // file.ext but not .hidden-file?
1160         if (dot > 0) {
1161             fileName.truncate (dot);
1162         }
1163 
1164     #if DEBUG_KP_MAIN_WINDOW
1165         qCDebug(kpLogMainWindow) << "kpMainWindow::sendDocumentNameToPrinter() fileName="
1166                    << fileName
1167                    << " dir="
1168                    << url.path();
1169     #endif
1170         printer->setDocName (fileName);
1171     }
1172 }
1173 
1174 //--------------------------------------------------------------------------------
1175 
1176 void kpMainWindow::setPrinterPageOrientation(QPrinter *printer)
1177 {
1178     const bool isLandscape = d->document->width() > d->document->height();
1179     printer->setPageOrientation(isLandscape ? QPageLayout::Landscape : QPageLayout::Portrait);
1180 }
1181 
1182 //--------------------------------------------------------------------------------
1183 
1184 void kpMainWindow::sendPreviewToPrinter(QPrinter *printer)
1185 {
1186   sendImageToPrinter(printer, false);
1187 }
1188 
1189 //--------------------------------------------------------------------------------
1190 // private
1191 void kpMainWindow::sendImageToPrinter (QPrinter *printer,
1192         bool showPrinterSetupDialog)
1193 {
1194     // Get image to be printed.
1195     kpImage image = d->document->imageWithSelection ();
1196 
1197 
1198     // Get image DPI.
1199     auto imageDotsPerMeterX = double (d->document->metaInfo ()->dotsPerMeterX ());
1200     auto imageDotsPerMeterY = double (d->document->metaInfo ()->dotsPerMeterY ());
1201 #if DEBUG_KP_MAIN_WINDOW
1202     qCDebug(kpLogMainWindow) << "kpMainWindow::sendImageToPrinter() image:"
1203                << " width=" << image.width ()
1204                << " height=" << image.height ()
1205                << " dotsPerMeterX=" << imageDotsPerMeterX
1206                << " dotsPerMeterY=" << imageDotsPerMeterY;
1207 #endif
1208 
1209     // Image DPI invalid (e.g. new image, could not read from file
1210     // or Qt3 doesn't implement DPI for JPEG)?
1211     if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0)
1212     {
1213         // Even if just one DPI dimension is invalid, mutate both DPI
1214         // dimensions as we have no information about the intended
1215         // aspect ratio anyway (and other dimension likely to be invalid).
1216 
1217         // When rendering text onto a document, the fonts are rasterised
1218         // according to the screen's DPI.
1219         // TODO: I think we should use the image's DPI.  Technically
1220         //       possible?
1221         //
1222         //       So no matter what computer you draw text on, you get
1223         //       the same pixels.
1224         //
1225         // So we must print at the screen's DPI to get the right text size.
1226         //
1227         // Unfortunately, this means that moving to a different screen DPI
1228         // affects printing.  If you edited the image at a different screen
1229         // DPI than when you print, you get incorrect results.  Furthermore,
1230         // this is bogus if you don't have text in your image.  Worse still,
1231         // what if you have multiple screens connected to the same computer
1232         // with different DPIs?
1233         // TODO: mysteriously, someone else is setting this to 96dpi always.
1234         QPixmap arbitraryScreenElement(1, 1);
1235         const QPaintDevice *screenDevice = &arbitraryScreenElement;
1236         const auto dpiX = screenDevice->logicalDpiX ();
1237         const auto dpiY = screenDevice->logicalDpiY ();
1238     #if DEBUG_KP_MAIN_WINDOW
1239         qCDebug(kpLogMainWindow) << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY;
1240     #endif
1241 
1242         imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER;
1243         imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER;
1244     }
1245 
1246 
1247     // Get page size (excluding margins).
1248     // Coordinate (0,0) is the X here:
1249     //     mmmmm
1250     //     mX  m
1251     //     m   m       m = margin
1252     //     m   m
1253     //     mmmmm
1254     const auto printerWidthMM = printer->widthMM ();
1255     const auto printerHeightMM = printer->heightMM ();
1256 
1257     auto dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER;
1258     auto dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER;
1259 
1260 #if DEBUG_KP_MAIN_WINDOW
1261     qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM
1262                << " heightMM=" << printerHeightMM;
1263 
1264     qCDebug(kpLogMainWindow) << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY;
1265 #endif
1266 
1267 
1268     //
1269     // If image doesn't fit on page at intended DPI, change the DPI.
1270     //
1271 
1272     const auto scaleDpiX =
1273         (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) / dpiX;
1274     const auto scaleDpiY =
1275         (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) / dpiY;
1276     const auto scaleDpi = qMax (scaleDpiX, scaleDpiY);
1277 
1278 #if DEBUG_KP_MAIN_WINDOW
1279     qCDebug(kpLogMainWindow) << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY
1280                << " --> scale at " << scaleDpi << " to fit?";
1281 #endif
1282 
1283     // Need to increase resolution to fit page?
1284     if (scaleDpi > 1.0)
1285     {
1286         dpiX *= scaleDpi;
1287         dpiY *= scaleDpi;
1288     #if DEBUG_KP_MAIN_WINDOW
1289         qCDebug(kpLogMainWindow) << "\t\t\tto fit page, scaled to:"
1290                    << " dpiX=" << dpiX << " dpiY=" << dpiY;
1291     #endif
1292     }
1293 
1294 
1295     // Make sure DPIs are equal as that's all QPrinter::setResolution()
1296     // supports.  We do this in such a way that we only ever stretch an
1297     // image, to avoid losing information.  Don't antialias as the printer
1298     // will do that to translate our DPI to its physical resolution and
1299     // double-antialiasing looks bad.
1300     if (dpiX > dpiY)
1301     {
1302     #if DEBUG_KP_MAIN_WINDOW
1303         qCDebug(kpLogMainWindow) << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX="
1304                    << dpiX;
1305     #endif
1306         kpPixmapFX::scale (&image,
1307              image.width (),
1308              qMax (1, qRound (image.height () * dpiX / dpiY)),
1309              false/*don't antialias*/);
1310 
1311         dpiY = dpiX;
1312     }
1313     else if (dpiY > dpiX)
1314     {
1315     #if DEBUG_KP_MAIN_WINDOW
1316         qCDebug(kpLogMainWindow) << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY="
1317                    << dpiY;
1318     #endif
1319         kpPixmapFX::scale (&image,
1320              qMax (1, qRound (image.width () * dpiY / dpiX)),
1321              image.height (),
1322              false/*don't antialias*/);
1323 
1324         dpiX = dpiY;
1325     }
1326 
1327     Q_ASSERT (dpiX == dpiY);
1328 
1329 
1330     // QPrinter::setResolution() has to be called before QPrinter::setup().
1331     printer->setResolution (qMax (1, qRound (dpiX)));
1332 
1333 
1334     sendDocumentNameToPrinter (printer);
1335 
1336 
1337     if (showPrinterSetupDialog)
1338     {
1339         auto *optionsPage = new kpPrintDialogPage (this);
1340         optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage);
1341 
1342         QPrintDialog printDialog (printer, this);
1343         printDialog.setOptionTabs ({optionsPage});
1344         printDialog.setWindowTitle (i18nc ("@title:window", "Print Image"));
1345 
1346         // Display dialog.
1347         const bool wantToPrint = printDialog.exec ();
1348 
1349         if (optionsPage->printImageCenteredOnPage () !=
1350             d->configPrintImageCenteredOnPage)
1351         {
1352             // Save config option even if the dialog was cancelled.
1353             d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage ();
1354 
1355             KConfigGroup cfg (KSharedConfig::openConfig (), QStringLiteral(kpSettingsGroupGeneral));
1356             cfg.writeEntry (kpSettingPrintImageCenteredOnPage,
1357                            d->configPrintImageCenteredOnPage);
1358             cfg.sync ();
1359         }
1360 
1361         if (!wantToPrint) {
1362             return;
1363         }
1364     }
1365 
1366 
1367     // Send image to printer.
1368     QPainter painter;
1369     painter.begin(printer);
1370 
1371     double originX = 0, originY = 0;
1372 
1373     // Center image on page?
1374     if (d->configPrintImageCenteredOnPage)
1375     {
1376         originX = (printer->width() - image.width ()) / 2;
1377         originY = (printer->height() - image.height ()) / 2;
1378     }
1379 
1380     painter.drawImage(qRound(originX), qRound(originY), image);
1381     painter.end();
1382 }
1383 
1384 //---------------------------------------------------------------------
1385 
1386 // private slot
1387 void kpMainWindow::slotPrint ()
1388 {
1389     toolEndShape ();
1390 
1391     QPrinter printer;
1392     setPrinterPageOrientation(&printer);
1393 
1394     sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/);
1395 }
1396 
1397 //---------------------------------------------------------------------
1398 
1399 // private slot
1400 void kpMainWindow::slotPrintPreview ()
1401 {
1402     toolEndShape ();
1403 
1404     QPrinter printer;
1405     setPrinterPageOrientation(&printer);
1406     QPrintPreviewDialog printPreview(&printer, this);
1407     connect(&printPreview, &QPrintPreviewDialog::paintRequested, this, &kpMainWindow::sendPreviewToPrinter);
1408 
1409     printPreview.exec ();
1410 }
1411 
1412 //---------------------------------------------------------------------
1413 
1414 // private slot
1415 void kpMainWindow::slotMail ()
1416 {
1417     toolEndShape ();
1418 
1419     if (d->document->url ().isEmpty ()/*no name*/ ||
1420         !(d->document->isFromExistingURL () && d->document->urlExists (d->document->url ())) ||
1421         d->document->isModified ()/*needs to be saved*/)
1422     {
1423 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1424         int result = KMessageBox::questionTwoActions(this,
1425 #else
1426         int result = KMessageBox::questionYesNo (this,
1427 #endif
1428                         i18n ("You must save this image before sending it.\n"
1429                               "Do you want to save it?"),
1430                         QString(),
1431                         KStandardGuiItem::save (), KStandardGuiItem::cancel ());
1432 
1433 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1434         if (result == KMessageBox::ButtonCode::PrimaryAction)
1435 #else
1436         if (result == KMessageBox::Yes)
1437 #endif
1438         {
1439             if (!save ())
1440             {
1441                 // save failed or aborted - don't email
1442                 return;
1443             }
1444         }
1445         else
1446         {
1447             // don't want to save - don't email
1448             return;
1449         }
1450     }
1451 
1452     auto *job = new KEMailClientLauncherJob;
1453     job->setSubject(d->document->prettyFilename());
1454     job->setAttachments({d->document->url()});
1455     job->start();
1456 }
1457 
1458 //---------------------------------------------------------------------
1459 
1460 // private
1461 bool kpMainWindow::queryCloseDocument ()
1462 {
1463     toolEndShape ();
1464 
1465     if (!d->document || !d->document->isModified ()) {
1466         return true;  // ok to close current doc
1467     }
1468 
1469 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1470     int result = KMessageBox::warningTwoActionsCancel(this,
1471 #else
1472     int result = KMessageBox::warningYesNoCancel (this,
1473 #endif
1474                      i18n ("The document \"%1\" has been modified.\n"
1475                            "Do you want to save it?",
1476                            d->document->prettyFilename ()),
1477                     QString()/*caption*/,
1478                     KStandardGuiItem::save (), KStandardGuiItem::discard ());
1479 
1480     switch (result)
1481     {
1482 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1483     case KMessageBox::ButtonCode::PrimaryAction:
1484 #else
1485     case KMessageBox::Yes:
1486 #endif
1487         return slotSave ();  // close only if save succeeds
1488 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1489     case KMessageBox::ButtonCode::SecondaryAction:
1490 #else
1491     case KMessageBox::No:
1492 #endif
1493         return true;  // close without saving
1494     default:
1495         return false;  // don't close current doc
1496     }
1497 }
1498 
1499 //---------------------------------------------------------------------
1500 
1501 // private virtual [base KMainWindow]
1502 bool kpMainWindow::queryClose ()
1503 {
1504 #if DEBUG_KP_MAIN_WINDOW
1505     qCDebug(kpLogMainWindow) << "kpMainWindow::queryClose()";
1506 #endif
1507     toolEndShape ();
1508 
1509     if (!queryCloseDocument ()) {
1510         return false;
1511     }
1512 
1513     if (!queryCloseColors ()) {
1514         return false;
1515     }
1516 
1517     return true;
1518 }
1519 
1520 //---------------------------------------------------------------------
1521 
1522 // private slot
1523 void kpMainWindow::slotClose ()
1524 {
1525     toolEndShape ();
1526 
1527     if (!queryCloseDocument ()) {
1528         return;
1529     }
1530 
1531     setDocument (nullptr);
1532 }
1533 
1534 //---------------------------------------------------------------------
1535 
1536 // private slot
1537 void kpMainWindow::slotQuit ()
1538 {
1539     toolEndShape ();
1540 
1541     close ();  // will call queryClose()
1542 }
1543 
1544 //---------------------------------------------------------------------