1 /*
2    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
3    Copyright (c) 2007 John Layt <john@layt.net>
4    Copyright (c) 2007,2011,2015 Martin Koller <kollix@aon.at>
5    All rights reserved.
6 
7    Redistribution and use in source and binary forms, with or without
8    modification, are permitted provided that the following conditions
9    are met:
10 
11    1. Redistributions of source code must retain the above copyright
12       notice, this list of conditions and the following disclaimer.
13    2. Redistributions in binary form must reproduce the above copyright
14       notice, this list of conditions and the following disclaimer in the
15       documentation and/or other materials provided with the distribution.
16 
17    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
29 
30 #include "kpMainWindow.h"
31 #include "kpMainWindowPrivate.h"
32 
33 #include <QAction>
34 #include <QDataStream>
35 #include <QDesktopWidget>
36 #include <QDialog>
37 #include <QDialogButtonBox>
38 #include <QFileDialog>
39 #include <QPainter>
40 #include <QPixmap>
41 #include <QSize>
42 #include <QPrinter>
43 #include <QPrintDialog>
44 #include <QScreen>
45 #include <QApplication>
46 #include <QTimer>
47 #include <QLabel>
48 #include <QCheckBox>
49 #include <QVBoxLayout>
50 #include <QImageReader>
51 #include <QImageWriter>
52 #include <QMimeDatabase>
53 #include <QPrintPreviewDialog>
54 
55 #include <KActionCollection>
56 #include <KSharedConfig>
57 #include <KConfigGroup>
58 #include <KFileCustomDialog>
59 #include <KPluralHandlingSpinBox>
60 #include <KMessageBox>
61 #include <KRecentFilesAction>
62 #include <KStandardShortcut>
63 #include <KStandardAction>
64 #include <KToolInvocation>
65 #include <KLocalizedString>
66 
67 #include "kpLogCategories.h"
68 #include "commands/kpCommandHistory.h"
69 #include "kpDefs.h"
70 #include "document/kpDocument.h"
71 #include "commands/imagelib/kpDocumentMetaInfoCommand.h"
72 #include "dialogs/imagelib/kpDocumentMetaInfoDialog.h"
73 #include "widgets/kpDocumentSaveOptionsWidget.h"
74 #include "pixmapfx/kpPixmapFX.h"
75 #include "widgets/kpPrintDialogPage.h"
76 #include "views/kpView.h"
77 #include "views/manager/kpViewManager.h"
78 
79 #if HAVE_KSANE
80 #include "../scan/sanedialog.h"
81 #endif // HAVE_KSANE
82 
83 // private
setupFileMenuActions()84 void kpMainWindow::setupFileMenuActions ()
85 {
86 #if DEBUG_KP_MAIN_WINDOW
87     qCDebug(kpLogMainWindow) << "kpMainWindow::setupFileMenuActions()";
88 #endif
89     KActionCollection *ac = actionCollection ();
90 
91     d->actionNew = KStandardAction::openNew (this, SLOT (slotNew()), ac);
92     d->actionOpen = KStandardAction::open (this, SLOT (slotOpen()), ac);
93 
94     d->actionOpenRecent = KStandardAction::openRecent(this, &kpMainWindow::slotOpenRecent, ac);
95     connect(d->actionOpenRecent, &KRecentFilesAction::recentListCleared, this, &kpMainWindow::slotRecentListCleared);
96     d->actionOpenRecent->loadEntries (KSharedConfig::openConfig ()->group (kpSettingsGroupRecentFiles));
97 #if DEBUG_KP_MAIN_WINDOW
98     qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
99 #endif
100 
101     d->actionSave = KStandardAction::save (this, SLOT (slotSave()), ac);
102     d->actionSaveAs = KStandardAction::saveAs (this, SLOT (slotSaveAs()), ac);
103 
104     d->actionExport = ac->addAction(QStringLiteral("file_export"));
105     d->actionExport->setText (i18n ("E&xport..."));
106     d->actionExport->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
107     connect (d->actionExport, &QAction::triggered, this, &kpMainWindow::slotExport);
108 
109     d->actionScan = ac->addAction(QStringLiteral("file_scan"));
110     d->actionScan->setText(i18n ("Scan..."));
111     d->actionScan->setIcon(QIcon::fromTheme("scanner"));
112 #if HAVE_KSANE
113     connect (d->actionScan, &QAction::triggered, this, &kpMainWindow::slotScan);
114 #else
115     d->actionScan->setEnabled(false);
116 #endif // HAVE_KSANE
117 
118     d->actionScreenshot = ac->addAction(QStringLiteral("file_screenshot"));
119     d->actionScreenshot->setText(i18n("Acquire Screenshot"));
120     connect (d->actionScreenshot, &QAction::triggered, this, &kpMainWindow::slotScreenshot);
121 
122     d->actionProperties = ac->addAction (QStringLiteral("file_properties"));
123     d->actionProperties->setText (i18n ("Properties"));
124     d->actionProperties->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
125     connect (d->actionProperties, &QAction::triggered, this, &kpMainWindow::slotProperties);
126 
127     //d->actionRevert = KStandardAction::revert (this, SLOT (slotRevert()), ac);
128     d->actionReload = ac->addAction (QStringLiteral("file_revert"));
129     d->actionReload->setText (i18n ("Reloa&d"));
130     d->actionReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
131     connect (d->actionReload, &QAction::triggered, this, &kpMainWindow::slotReload);
132     ac->setDefaultShortcuts (d->actionReload, KStandardShortcut::reload ());
133     slotEnableReload ();
134 
135     d->actionPrint = KStandardAction::print (this, SLOT (slotPrint()), ac);
136     d->actionPrintPreview = KStandardAction::printPreview (this, SLOT (slotPrintPreview()), ac);
137 
138     d->actionMail = KStandardAction::mail (this, SLOT (slotMail()), ac);
139 
140     d->actionClose = KStandardAction::close (this, SLOT (slotClose()), ac);
141     d->actionQuit = KStandardAction::quit (this, SLOT (slotQuit()), ac);
142 
143     d->scanDialog = nullptr;
144 
145     enableFileMenuDocumentActions (false);
146 }
147 
148 //---------------------------------------------------------------------
149 
150 // private
enableFileMenuDocumentActions(bool enable)151 void kpMainWindow::enableFileMenuDocumentActions (bool enable)
152 {
153     // d->actionNew
154     // d->actionOpen
155 
156     // d->actionOpenRecent
157 
158     d->actionSave->setEnabled (enable);
159     d->actionSaveAs->setEnabled (enable);
160 
161     d->actionExport->setEnabled (enable);
162 
163     // d->actionScan
164 
165     d->actionProperties->setEnabled (enable);
166 
167     // d->actionReload
168 
169     d->actionPrint->setEnabled (enable);
170     d->actionPrintPreview->setEnabled (enable);
171 
172     d->actionMail->setEnabled (enable);
173 
174     d->actionClose->setEnabled (enable);
175     // d->actionQuit->setEnabled (enable);
176 }
177 
178 //---------------------------------------------------------------------
179 
180 // private
addRecentURL(const QUrl & url_)181 void kpMainWindow::addRecentURL (const QUrl &url_)
182 {
183     // HACK: KRecentFilesAction::loadEntries() clears the KRecentFilesAction::d->urls
184     //       map.
185     //
186     //       So afterwards, the URL ref, our method is given, points to an
187     //       element in this now-cleared map (see KRecentFilesAction::urlSelected(QAction*)).
188     //       Accessing it would result in a crash.
189     //
190     //       To avoid the crash, make a copy of it before calling
191     //       loadEntries() and use this copy, instead of the to-be-dangling
192     //       ref.
193     const QUrl url = url_; // DO NOT MAKE IT A REFERENCE, THE CALL BELOW TO loadEntries DESTROYS url_
194 
195 #if DEBUG_KP_MAIN_WINDOW
196     qCDebug(kpLogMainWindow) << "kpMainWindow::addRecentURL(" << url << ")";
197 #endif
198     if (url.isEmpty ())
199         return;
200 
201 
202     KSharedConfig::Ptr cfg = KSharedConfig::openConfig();
203 
204     // KConfig::readEntry() does not actually reread from disk, hence doesn't
205     // realize what other processes have done e.g. Settings / Show Path
206     cfg->reparseConfiguration ();
207 
208 #if DEBUG_KP_MAIN_WINDOW
209     qCDebug(kpLogMainWindow) << "\trecent URLs=" << d->actionOpenRecent->items ();
210 #endif
211     // HACK: Something might have changed interprocess.
212     // If we could PROPAGATE: interprocess, then this wouldn't be required.
213     d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles));
214 #if DEBUG_KP_MAIN_WINDOW
215     qCDebug(kpLogMainWindow) << "\tafter loading config=" << d->actionOpenRecent->items ();
216 #endif
217 
218     d->actionOpenRecent->addUrl (url);
219 
220     d->actionOpenRecent->saveEntries (cfg->group (kpSettingsGroupRecentFiles));
221     cfg->sync ();
222 
223 #if DEBUG_KP_MAIN_WINDOW
224     qCDebug(kpLogMainWindow) << "\tnew recent URLs=" << d->actionOpenRecent->items ();
225 #endif
226 
227 
228     // TODO: PROPAGATE: interprocess
229     // TODO: Is this loop safe since a KMainWindow later along in the list,
230     //       could be closed as the code in the body almost certainly re-enters
231     //       the event loop?  Problem for KDE 3 as well, I think.
232     for (auto *kmw : KMainWindow::memberList ())
233     {
234         Q_ASSERT (dynamic_cast <kpMainWindow *> (kmw));
235         auto *mw = dynamic_cast <kpMainWindow *> (kmw);
236 
237     #if DEBUG_KP_MAIN_WINDOW
238         qCDebug(kpLogMainWindow) << "\t\tmw=" << mw;
239     #endif
240 
241         if (mw != this)
242         {
243             // WARNING: Do not use KRecentFilesAction::setItems()
244             //          - it does not work since only its superclass,
245             //          KSelectAction, implements setItems() and can't
246             //          update KRecentFilesAction's URL list.
247 
248             // Avoid URL memory leak in KRecentFilesAction::loadEntries().
249             mw->d->actionOpenRecent->clear ();
250 
251             mw->d->actionOpenRecent->loadEntries (cfg->group (kpSettingsGroupRecentFiles));
252         #if DEBUG_KP_MAIN_WINDOW
253             qCDebug(kpLogMainWindow) << "\t\t\tcheck recent URLs="
254                         << mw->d->actionOpenRecent->items ();
255         #endif
256         }
257     }
258 }
259 
260 //---------------------------------------------------------------------
261 
262 
263 // private slot
264 // TODO: Disable action if
265 //       (d->configOpenImagesInSameWindow && d->document && d->document->isEmpty())
266 //       as it does nothing if this is true.
slotNew()267 void kpMainWindow::slotNew ()
268 {
269     toolEndShape ();
270 
271     if (d->document && !d->configOpenImagesInSameWindow)
272     {
273         // A document -- empty or otherwise -- is open.
274         // Force open a new window.  In contrast, open() might not open
275         // a new window in this case.
276         auto *win = new kpMainWindow ();
277         win->show ();
278     }
279     else
280     {
281         open (QUrl (), true/*create an empty doc*/);
282     }
283 }
284 
285 //---------------------------------------------------------------------
286 
287 
288 // private
defaultDocSize() const289 QSize kpMainWindow::defaultDocSize () const
290 {
291     // KConfig::readEntry() does not actually reread from disk, hence doesn't
292     // realize what other processes have done e.g. Settings / Show Path
293     KSharedConfig::openConfig ()->reparseConfiguration ();
294 
295     KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral);
296 
297     QSize docSize = cfg.readEntry (kpSettingLastDocSize, QSize ());
298 
299     if (docSize.isEmpty ())
300     {
301         docSize = QSize (400, 300);
302     }
303     else
304     {
305         // Don't get too big or you'll thrash (or even lock up) the computer
306         // just by opening a window
307         docSize = QSize (qMin (2048, docSize.width ()),
308                          qMin (2048, docSize.height ()));
309     }
310 
311     return docSize;
312 }
313 
314 //---------------------------------------------------------------------
315 
316 // private
saveDefaultDocSize(const QSize & size)317 void kpMainWindow::saveDefaultDocSize (const QSize &size)
318 {
319 #if DEBUG_KP_MAIN_WINDOW
320     qCDebug(kpLogMainWindow) << "\tCONFIG: saving Last Doc Size = " << size;
321 #endif
322 
323     KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral);
324 
325     cfg.writeEntry (kpSettingLastDocSize, size);
326     cfg.sync ();
327 }
328 
329 //---------------------------------------------------------------------
330 
331 // private
shouldOpen()332 bool kpMainWindow::shouldOpen ()
333 {
334     if (d->configOpenImagesInSameWindow)
335     {
336     #if DEBUG_KP_MAIN_WINDOW
337         qCDebug(kpLogMainWindow) << "\topenImagesInSameWindow";
338     #endif
339         // (this brings up a dialog and might save the current doc)
340         if (!queryCloseDocument ())
341         {
342         #if DEBUG_KP_MAIN_WINDOW
343             qCDebug(kpLogMainWindow) << "\t\tqueryCloseDocument() aborts open";
344         #endif
345             return false;
346         }
347     }
348 
349     return true;
350 }
351 
352 //---------------------------------------------------------------------
353 
354 // private
setDocumentChoosingWindow(kpDocument * doc)355 void kpMainWindow::setDocumentChoosingWindow (kpDocument *doc)
356 {
357     // Want new window?
358     if (d->document && !d->document->isEmpty () &&
359         !d->configOpenImagesInSameWindow)
360     {
361         // Send doc to new window.
362         auto *win = new kpMainWindow (doc);
363         win->show ();
364     }
365     else
366     {
367         // (sets up views, doc signals)
368         setDocument (doc);
369     }
370 }
371 
372 //---------------------------------------------------------------------
373 
374 // private
openInternal(const QUrl & url,const QSize & fallbackDocSize,bool newDocSameNameIfNotExist)375 kpDocument *kpMainWindow::openInternal (const QUrl &url,
376         const QSize &fallbackDocSize,
377         bool newDocSameNameIfNotExist)
378 {
379     // If using OpenImagesInSameWindow mode, ask whether to close the
380     // current document.
381     if (!shouldOpen ())
382         return nullptr;
383 
384     // Create/open doc.
385     auto *newDoc = new kpDocument (fallbackDocSize.width (),
386                                    fallbackDocSize.height (), documentEnvironment ());
387 
388     if (!newDoc->open (url, newDocSameNameIfNotExist))
389     {
390     #if DEBUG_KP_MAIN_WINDOW
391         qCDebug(kpLogMainWindow) << "\topen failed";
392     #endif
393         delete newDoc;
394         return nullptr;
395     }
396 
397 #if DEBUG_KP_MAIN_WINDOW
398     qCDebug(kpLogMainWindow) << "\topen OK";
399 #endif
400     // Send document to current or new window.
401     setDocumentChoosingWindow (newDoc);
402 
403     return newDoc;
404 }
405 
406 //---------------------------------------------------------------------
407 
408 // private
open(const QUrl & url,bool newDocSameNameIfNotExist)409 bool kpMainWindow::open (const QUrl &url, bool newDocSameNameIfNotExist)
410 {
411 #if DEBUG_KP_MAIN_WINDOW
412     qCDebug(kpLogMainWindow) << "kpMainWindow::open(" << url
413               << ",newDocSameNameIfNotExist=" << newDocSameNameIfNotExist
414               << ")";
415 #endif
416 
417     kpDocument *newDoc = openInternal (url,
418                                        defaultDocSize (),
419                                        newDocSameNameIfNotExist);
420     if (newDoc)
421     {
422         if (newDoc->isFromExistingURL ())
423             addRecentURL (url);
424         return true;
425     }
426 
427     return false;
428 }
429 
430 //---------------------------------------------------------------------
431 
432 // private
askForOpenURLs(const QString & caption,bool allowMultipleURLs)433 QList<QUrl> kpMainWindow::askForOpenURLs(const QString &caption, bool allowMultipleURLs)
434 {
435   QMimeDatabase db;
436   QStringList filterList;
437   QString filter;
438   for (const auto &type : QImageReader::supportedMimeTypes())
439   {
440     if ( !filter.isEmpty() ) {
441       filter += QLatin1Char(' ');
442     }
443 
444     QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type)));
445     if ( mime.isValid() )
446     {
447       QString glob = mime.globPatterns().join(QLatin1Char(' '));
448 
449       filter += glob;
450 
451       // I want to show the mime comment AND the file glob pattern,
452       // but to avoid that the "All Supported Files" entry shows ALL glob patterns,
453       // I must add the pattern here a second time so that QFileDialog::HideNameFilterDetails
454       // can hide the first pattern and I still see the second one
455       filterList << mime.comment() + QStringLiteral(" (%1)(%2)").arg(glob).arg(glob);
456     }
457   }
458 
459   filterList.prepend(i18n("All Supported Files (%1)", filter));
460 
461   QFileDialog fd(this);
462   fd.setNameFilters(filterList);
463   fd.setOption(QFileDialog::HideNameFilterDetails);
464   fd.setWindowTitle(caption);
465 
466   if ( allowMultipleURLs ) {
467     fd.setFileMode(QFileDialog::ExistingFiles);
468   }
469 
470   if ( fd.exec() ) {
471     return fd.selectedUrls();
472   }
473 
474   return {};
475 }
476 
477 //---------------------------------------------------------------------
478 
479 // private slot
slotOpen()480 void kpMainWindow::slotOpen ()
481 {
482     toolEndShape ();
483 
484     const QList<QUrl> urls = askForOpenURLs(i18nc("@title:window", "Open Image"));
485 
486     for (const auto & url : urls)
487     {
488         open (url);
489     }
490 }
491 
492 //---------------------------------------------------------------------
493 
494 // private slot
slotOpenRecent(const QUrl & url)495 void kpMainWindow::slotOpenRecent (const QUrl &url)
496 {
497 #if DEBUG_KP_MAIN_WINDOW
498     qCDebug(kpLogMainWindow) << "kpMainWindow::slotOpenRecent(" << url << ")";
499     qCDebug(kpLogMainWindow) << "\titems=" << d->actionOpenRecent->items ();
500 #endif
501 
502     toolEndShape ();
503 
504     open (url);
505 
506     // If the open is successful, addRecentURL() would have bubbled up the
507     // URL in the File / Open Recent action.  As a side effect, the URL is
508     // deselected.
509     //
510     // If the open fails, we should deselect the URL:
511     //
512     // 1. for consistency
513     //
514     // 2. because it has not been opened.
515     //
516     d->actionOpenRecent->setCurrentItem (-1);
517 }
518 
519 //---------------------------------------------------------------------
520 
slotRecentListCleared()521 void kpMainWindow::slotRecentListCleared()
522 {
523   d->actionOpenRecent->saveEntries(KSharedConfig::openConfig()->group(kpSettingsGroupRecentFiles));
524 }
525 
526 //---------------------------------------------------------------------
527 
528 #if HAVE_KSANE
529 // private slot
slotScan()530 void kpMainWindow::slotScan ()
531 {
532 #if DEBUG_KP_MAIN_WINDOW
533     qCDebug(kpLogMainWindow) << "kpMainWindow::slotScan() scanDialog=" << d->scanDialog;
534 #endif
535 
536     toolEndShape ();
537 
538     if (!d->scanDialog)
539     {
540         // Create scan dialog
541         d->scanDialog = new SaneDialog(this);
542 
543         // No scanning support (kdegraphics/libkscan) installed?
544         if (!d->scanDialog)
545         {
546             KMessageBox::sorry (this,
547                                 i18n("Failed to open scanning dialog."),
548                                 i18nc("@title:window", "Scanning Failed"));
549             return;
550         }
551 
552     #if DEBUG_KP_MAIN_WINDOW
553         qCDebug(kpLogMainWindow) << "\tcreated scanDialog=" << d->scanDialog;
554     #endif
555         connect (d->scanDialog, &SaneDialog::finalImage, this, &kpMainWindow::slotScanned);
556     }
557 
558 
559     // If using OpenImagesInSameWindow mode, ask whether to close the
560     // current document.
561     //
562     // Do this after scan support is detected.  Because if it's not, what
563     // would be the point of closing the document?
564     //
565     // Ideally, we would do this after the user presses "Final Scan" in
566     // the scan dialog and before the scan begins (if the user wants to
567     // cancel the scan operation, it would be annoying to offer this choice
568     // only after the slow scan is completed) but the KScanDialog API does
569     // not allow this.  So we settle for doing this before any
570     // scan dialogs are shown.  We don't do this between KScanDialog::setup()
571     // and KScanDialog::exec() as it could be confusing alternating between
572     // scanning and KolourPaint dialogs.
573     if (!shouldOpen ()) {
574         return;
575     }
576 
577 
578 #if DEBUG_KP_MAIN_WINDOW
579     qCDebug(kpLogMainWindow) << "\tcalling setup";
580 #endif
581     // Bring up dialog to select scan device.
582     // If there is no scanner, we find that this does not bring up a dialog
583     // but still returns true.
584     if (d->scanDialog->setup ())
585     {
586     #if DEBUG_KP_MAIN_WINDOW
587         qCDebug(kpLogMainWindow) << "\t\tOK - showing dialog";
588     #endif
589         // Called only if scanner configured/available.
590         //
591         // In reality, this seems to be called even if you press "Cancel" in
592         // the KScanDialog::setup() dialog!
593         //
594         // We use exec() to make sure it's modal.  show() seems to work too
595         // but better safe than sorry.
596         d->scanDialog->exec ();
597     }
598     else
599     {
600         // Have never seen this code path execute even if "Cancel" is pressed.
601     #if DEBUG_KP_MAIN_WINDOW
602         qCDebug(kpLogMainWindow) << "\t\tFAIL";
603     #endif
604     }
605 }
606 
607 //---------------------------------------------------------------------
608 
609 // private slot
slotScanned(const QImage & image,int)610 void kpMainWindow::slotScanned (const QImage &image, int)
611 {
612 #if DEBUG_KP_MAIN_WINDOW
613     qCDebug(kpLogMainWindow) << "kpMainWindow::slotScanned() image.rect=" << image.rect ();
614 #endif
615 
616 #if DEBUG_KP_MAIN_WINDOW
617     qCDebug(kpLogMainWindow) << "\thiding dialog";
618 #endif
619     // (KScanDialog does not close itself after a scan is made)
620     //
621     // Close the dialog, first thing:
622     //
623     // 1. This means that any dialogs we bring up won't be nested on top.
624     //
625     // 2. We don't want to return from this method but forget to close
626     //    the dialog.  So do it before anything else.
627     d->scanDialog->hide ();
628 
629     // (just in case there's some drawing between slotScan() exiting and
630     //  us being called)
631     toolEndShape ();
632 
633 
634     // TODO: Maybe this code should be moved into kpdocument.cpp -
635     //       since it resembles the responsibilities of kpDocument::open().
636 
637     kpDocumentSaveOptions saveOptions;
638     kpDocumentMetaInfo metaInfo;
639 
640     kpDocument::getDataFromImage(image, saveOptions, metaInfo);
641 
642     // Create document from image and meta info.
643     auto *doc = new kpDocument (image.width (), image.height (), documentEnvironment ());
644     doc->setImage (image);
645     doc->setSaveOptions (saveOptions);
646     doc->setMetaInfo (metaInfo);
647 
648     // Send document to current or new window.
649     setDocumentChoosingWindow (doc);
650 }
651 #endif // HAVE_KSANE
652 
653 //---------------------------------------------------------------------
654 
slotScreenshot()655 void kpMainWindow::slotScreenshot()
656 {
657   toolEndShape();
658 
659   auto *dialog = new QDialog(this);
660   auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
661                                                    QDialogButtonBox::Cancel, dialog);
662   connect (buttons, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
663   connect (buttons, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
664 
665   auto *label = new QLabel(i18n("Snapshot Delay"));
666   auto *seconds = new KPluralHandlingSpinBox;
667   seconds->setRange(0, 99);
668   seconds->setSuffix(ki18np(" second", " seconds"));
669   seconds->setSpecialValueText(i18n("No delay"));
670 
671   auto *hideWindow = new QCheckBox(i18n("Hide Main Window"));
672   hideWindow->setChecked(true);
673 
674   auto *vbox = new QVBoxLayout(dialog);
675   vbox->addWidget(label);
676   vbox->addWidget(seconds);
677   vbox->addWidget(hideWindow);
678   vbox->addWidget(buttons);
679 
680   if ( dialog->exec() == QDialog::Rejected )
681   {
682     delete dialog;
683     return;
684   }
685 
686   if ( hideWindow->isChecked() ) {
687     hide();
688   }
689 
690   // at least 1 seconds to make sure the window is hidden and the hide effect already stopped
691   QTimer::singleShot((seconds->value() + 1) * 1000, this, &kpMainWindow::slotMakeScreenshot);
692 
693   delete dialog;
694 }
695 
696 //---------------------------------------------------------------------
697 
slotMakeScreenshot()698 void kpMainWindow::slotMakeScreenshot()
699 {
700   QCoreApplication::processEvents();
701   QPixmap pixmap = QGuiApplication::primaryScreen()->grabWindow(QApplication::desktop()->winId());
702 
703   auto *doc = new kpDocument(pixmap.width(), pixmap.height(), documentEnvironment());
704   doc->setImage(pixmap.toImage());
705 
706   // Send document to current or new window.
707   setDocumentChoosingWindow(doc);
708 
709   show();  // in case we hid the mainwindow, show it again
710 }
711 
712 //---------------------------------------------------------------------
713 
714 // private slot
slotProperties()715 void kpMainWindow::slotProperties ()
716 {
717     toolEndShape ();
718 
719     kpDocumentMetaInfoDialog dialog (document ()->metaInfo (), this);
720 
721     if (dialog.exec () && !dialog.isNoOp ())
722     {
723         commandHistory ()->addCommand (
724             new kpDocumentMetaInfoCommand (
725                 i18n ("Document Properties"),
726                 dialog.metaInfo ()/*new*/, *document ()->metaInfo ()/*old*/,
727                 commandEnvironment ()));
728     }
729 }
730 
731 //---------------------------------------------------------------------
732 
733 // private slot
save(bool localOnly)734 bool kpMainWindow::save (bool localOnly)
735 {
736     if (d->document->url ().isEmpty () ||
737         !QImageWriter::supportedMimeTypes()
738             .contains(d->document->saveOptions ()->mimeType().toLatin1()) ||
739         // SYNC: kpDocument::getPixmapFromFile() can't determine quality
740         //       from file so it has been set initially to an invalid value.
741         (d->document->saveOptions ()->mimeTypeHasConfigurableQuality () &&
742             d->document->saveOptions ()->qualityIsInvalid ()) ||
743         (localOnly && !d->document->url ().isLocalFile ()))
744     {
745         return saveAs (localOnly);
746     }
747 
748     if (d->document->save (!d->document->savedAtLeastOnceBefore ()/*lossy prompt*/))
749     {
750         addRecentURL (d->document->url ());
751         return true;
752     }
753 
754     return false;
755 }
756 
757 //---------------------------------------------------------------------
758 
759 // private slot
slotSave()760 bool kpMainWindow::slotSave ()
761 {
762     toolEndShape ();
763 
764     return save ();
765 }
766 
767 //---------------------------------------------------------------------
768 
769 // private
askForSaveURL(const QString & caption,const QString & startURL,const kpImage & imageToBeSaved,const kpDocumentSaveOptions & startSaveOptions,const kpDocumentMetaInfo & docMetaInfo,const QString & forcedSaveOptionsGroup,bool localOnly,kpDocumentSaveOptions * chosenSaveOptions,bool isSavingForFirstTime,bool * allowLossyPrompt)770 QUrl kpMainWindow::askForSaveURL (const QString &caption,
771                                   const QString &startURL,
772                                   const kpImage &imageToBeSaved,
773                                   const kpDocumentSaveOptions &startSaveOptions,
774                                   const kpDocumentMetaInfo &docMetaInfo,
775                                   const QString &forcedSaveOptionsGroup,
776                                   bool localOnly,
777                                   kpDocumentSaveOptions *chosenSaveOptions,
778                                   bool isSavingForFirstTime,
779                                   bool *allowLossyPrompt)
780 {
781 #if DEBUG_KP_MAIN_WINDOW
782     qCDebug(kpLogMainWindow) << "kpMainWindow::askForURL() startURL=" << startURL;
783     startSaveOptions.printDebug ("\tstartSaveOptions");
784 #endif
785 
786     bool reparsedConfiguration = false;
787 
788     // KConfig::readEntry() does not actually reread from disk, hence doesn't
789     // realize what other processes have done e.g. Settings / Show Path
790     // so reparseConfiguration() must be called
791 #define SETUP_READ_CFG()                                                             \
792     if (!reparsedConfiguration)                                                      \
793     {                                                                                \
794         KSharedConfig::openConfig ()->reparseConfiguration ();                       \
795         reparsedConfiguration = true;                                                \
796     }                                                                                \
797                                                                                      \
798     KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup);
799 
800 
801     if (chosenSaveOptions) {
802         *chosenSaveOptions = kpDocumentSaveOptions ();
803     }
804 
805     if (allowLossyPrompt) {
806         *allowLossyPrompt = true;  // play it safe for now
807     }
808 
809 
810     kpDocumentSaveOptions fdSaveOptions = startSaveOptions;
811 
812     QStringList mimeTypes;
813     for (const auto &type : QImageWriter::supportedMimeTypes()) {
814       mimeTypes << QString::fromLatin1(type);
815     }
816 #if DEBUG_KP_MAIN_WINDOW
817     QStringList sortedMimeTypes = mimeTypes;
818     sortedMimeTypes.sort ();
819     qCDebug(kpLogMainWindow) << "\tmimeTypes=" << mimeTypes
820                << "\tsortedMimeTypes=" << sortedMimeTypes;
821 #endif
822     if (mimeTypes.isEmpty ())
823     {
824         qCCritical(kpLogMainWindow) << "No output mimetypes!";
825         return {};
826     }
827 
828 #define MIME_TYPE_IS_VALID() (!fdSaveOptions.mimeTypeIsInvalid () &&                 \
829                               mimeTypes.contains (fdSaveOptions.mimeType ()))
830     if (!MIME_TYPE_IS_VALID ())
831     {
832     #if DEBUG_KP_MAIN_WINDOW
833         qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType ()
834                    << " not valid, get default";
835     #endif
836 
837         SETUP_READ_CFG ();
838 
839         fdSaveOptions.setMimeType (kpDocumentSaveOptions::defaultMimeType (cfg));
840 
841 
842         if (!MIME_TYPE_IS_VALID ())
843         {
844         #if DEBUG_KP_MAIN_WINDOW
845             qCDebug(kpLogMainWindow) << "\tmimeType=" << fdSaveOptions.mimeType ()
846                        << " not valid, get hardcoded";
847         #endif
848             if (mimeTypes.contains(QLatin1String("image/png"))) {
849                 fdSaveOptions.setMimeType (QStringLiteral("image/png"));
850             }
851             else if (mimeTypes.contains(QLatin1String("image/bmp"))) {
852                 fdSaveOptions.setMimeType (QStringLiteral("image/bmp"));
853             }
854             else {
855                 fdSaveOptions.setMimeType (mimeTypes.first ());
856             }
857         }
858     }
859 #undef MIME_TYPE_IS_VALID
860 
861     if (fdSaveOptions.colorDepthIsInvalid ())
862     {
863         SETUP_READ_CFG ();
864 
865         fdSaveOptions.setColorDepth (kpDocumentSaveOptions::defaultColorDepth (cfg));
866         fdSaveOptions.setDither (kpDocumentSaveOptions::defaultDither (cfg));
867     }
868 
869     if (fdSaveOptions.qualityIsInvalid ())
870     {
871         SETUP_READ_CFG ();
872 
873         fdSaveOptions.setQuality (kpDocumentSaveOptions::defaultQuality (cfg));
874     }
875 #if DEBUG_KP_MAIN_WINDOW
876     fdSaveOptions.printDebug ("\tcorrected saveOptions passed to fileDialog");
877 #endif
878 
879     auto *saveOptionsWidget =
880         new kpDocumentSaveOptionsWidget (imageToBeSaved,
881             fdSaveOptions,
882             docMetaInfo,
883             this);
884 
885     KFileCustomDialog fd (QUrl (startURL), this);
886     fd.setOperationMode (KFileWidget::Saving);
887     fd.setWindowTitle (caption);
888     fd.setCustomWidget (saveOptionsWidget);
889     KFileWidget *fw = fd.fileWidget();
890     fw->setConfirmOverwrite (true);
891     fw->setMimeFilter (mimeTypes, fdSaveOptions.mimeType ());
892     if (localOnly) {
893         fw->setMode (KFile::File | KFile::LocalOnly);
894     }
895 
896     saveOptionsWidget->setVisualParent (&fd);
897 
898     connect (fw, &KFileWidget::filterChanged,
899              saveOptionsWidget, &kpDocumentSaveOptionsWidget::setMimeType);
900 
901     if ( fd.exec() == QDialog::Accepted )
902     {
903         kpDocumentSaveOptions newSaveOptions = saveOptionsWidget->documentSaveOptions ();
904     #if DEBUG_KP_MAIN_WINDOW
905         newSaveOptions.printDebug ("\tnewSaveOptions");
906     #endif
907 
908         KConfigGroup cfg (KSharedConfig::openConfig (), forcedSaveOptionsGroup);
909 
910         // Save options user forced - probably want to use them in future
911         kpDocumentSaveOptions::saveDefaultDifferences (cfg,
912             fdSaveOptions, newSaveOptions);
913         cfg.sync ();
914 
915 
916         if (chosenSaveOptions) {
917             *chosenSaveOptions = newSaveOptions;
918         }
919 
920         const QList<QUrl> selectedUrls = fw->selectedUrls ();
921         if (selectedUrls.isEmpty()) { // shouldn't happen
922             return {};
923         }
924         const QUrl selectedUrl = selectedUrls.at(0);
925 
926         if (allowLossyPrompt)
927         {
928             // SYNC: kpDocumentSaveOptions elements - everything except quality
929             //       (one quality setting is "just as lossy" as another so no
930             //        need to continually warn due to quality change)
931             *allowLossyPrompt =
932                 (isSavingForFirstTime ||
933                  selectedUrl != QUrl (startURL) ||
934                  newSaveOptions.mimeType () != startSaveOptions.mimeType () ||
935                  newSaveOptions.colorDepth () != startSaveOptions.colorDepth () ||
936                  newSaveOptions.dither () != startSaveOptions.dither ());
937         #if DEBUG_KP_MAIN_WINDOW
938             qCDebug(kpLogMainWindow) << "\tallowLossyPrompt=" << *allowLossyPrompt;
939         #endif
940         }
941 
942 
943     #if DEBUG_KP_MAIN_WINDOW
944         qCDebug(kpLogMainWindow) << "\tselectedUrl=" << selectedUrl;
945     #endif
946         return selectedUrl;
947     }
948 
949     return {};
950 #undef SETUP_READ_CFG
951 }
952 
953 //---------------------------------------------------------------------
954 
955 // private slot
saveAs(bool localOnly)956 bool kpMainWindow::saveAs (bool localOnly)
957 {
958     kpDocumentSaveOptions chosenSaveOptions;
959     bool allowLossyPrompt;
960     QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Save Image As"),
961                                     d->document->url ().url (),
962                                     d->document->imageWithSelection (),
963                                     *d->document->saveOptions (),
964                                     *d->document->metaInfo (),
965                                     kpSettingsGroupFileSaveAs,
966                                     localOnly,
967                                     &chosenSaveOptions,
968                                     !d->document->savedAtLeastOnceBefore (),
969                                     &allowLossyPrompt);
970 
971 
972     if (chosenURL.isEmpty ()) {
973         return false;
974     }
975 
976 
977     if (!d->document->saveAs (chosenURL, chosenSaveOptions,
978                              allowLossyPrompt))
979     {
980         return false;
981     }
982 
983 
984     addRecentURL (chosenURL);
985 
986     return true;
987 }
988 
989 //---------------------------------------------------------------------
990 
991 // private slot
slotSaveAs()992 bool kpMainWindow::slotSaveAs ()
993 {
994     toolEndShape ();
995 
996     return saveAs ();
997 }
998 
999 //---------------------------------------------------------------------
1000 
1001 // private slot
slotExport()1002 bool kpMainWindow::slotExport ()
1003 {
1004     toolEndShape ();
1005 
1006     kpDocumentSaveOptions chosenSaveOptions;
1007     bool allowLossyPrompt;
1008     QUrl chosenURL = askForSaveURL (i18nc ("@title:window", "Export"),
1009                                     d->lastExportURL.url (),
1010                                     d->document->imageWithSelection (),
1011                                     d->lastExportSaveOptions,
1012                                     *d->document->metaInfo (),
1013                                     kpSettingsGroupFileExport,
1014                                     false/*allow remote files*/,
1015                                     &chosenSaveOptions,
1016                                     d->exportFirstTime,
1017                                     &allowLossyPrompt);
1018 
1019 
1020     if (chosenURL.isEmpty ()) {
1021         return false;
1022     }
1023 
1024     if (!kpDocument::savePixmapToFile (d->document->imageWithSelection (),
1025                                        chosenURL,
1026                                        chosenSaveOptions, *d->document->metaInfo (),
1027                                        allowLossyPrompt,
1028                                        this))
1029     {
1030         return false;
1031     }
1032 
1033 
1034     addRecentURL (chosenURL);
1035 
1036     d->lastExportURL = chosenURL;
1037     d->lastExportSaveOptions = chosenSaveOptions;
1038 
1039     d->exportFirstTime = false;
1040 
1041     return true;
1042 }
1043 
1044 //---------------------------------------------------------------------
1045 
1046 // private slot
slotEnableReload()1047 void kpMainWindow::slotEnableReload ()
1048 {
1049     d->actionReload->setEnabled (d->document);
1050 }
1051 
1052 //---------------------------------------------------------------------
1053 
1054 // private slot
slotReload()1055 bool kpMainWindow::slotReload ()
1056 {
1057     toolEndShape ();
1058 
1059     Q_ASSERT (d->document);
1060 
1061 
1062     QUrl oldURL = d->document->url ();
1063 
1064 
1065     if (d->document->isModified ())
1066     {
1067         int result = KMessageBox::Cancel;
1068 
1069         if (d->document->isFromExistingURL () && !oldURL.isEmpty ())
1070         {
1071             result = KMessageBox::warningContinueCancel (this,
1072                          i18n ("The document \"%1\" has been modified.\n"
1073                                "Reloading will lose all changes since you last saved it.\n"
1074                                "Are you sure?",
1075                                d->document->prettyFilename ()),
1076                          QString()/*caption*/,
1077                          KGuiItem(i18n ("&Reload")));
1078         }
1079         else
1080         {
1081             result = KMessageBox::warningContinueCancel (this,
1082                          i18n ("The document \"%1\" has been modified.\n"
1083                                "Reloading will lose all changes.\n"
1084                                "Are you sure?",
1085                                d->document->prettyFilename ()),
1086                          QString()/*caption*/,
1087                          KGuiItem(i18n ("&Reload")));
1088         }
1089 
1090         if (result != KMessageBox::Continue) {
1091             return false;
1092         }
1093     }
1094 
1095 
1096     kpDocument *doc = nullptr;
1097 
1098     // If it's _supposed to_ come from an existing URL or it actually exists
1099     if (d->document->isFromExistingURL () ||
1100         d->document->urlExists (oldURL))
1101     {
1102     #if DEBUG_KP_MAIN_WINDOW
1103         qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() reloading from disk!";
1104     #endif
1105 
1106         doc = new kpDocument (1, 1, documentEnvironment ());
1107         if (!doc->open (oldURL))
1108         {
1109             delete doc; doc = nullptr;
1110             return false;
1111         }
1112 
1113         addRecentURL (oldURL);
1114     }
1115     else
1116     {
1117     #if DEBUG_KP_MAIN_WINDOW
1118         qCDebug(kpLogMainWindow) << "kpMainWindow::slotReload() create doc";
1119     #endif
1120 
1121         doc = new kpDocument (d->document->constructorWidth (),
1122                               d->document->constructorHeight (),
1123                               documentEnvironment ());
1124         doc->setURL (oldURL, false/*not from URL*/);
1125     }
1126 
1127 
1128     setDocument (doc);
1129 
1130     return true;
1131 }
1132 
1133 
1134 // private
sendDocumentNameToPrinter(QPrinter * printer)1135 void kpMainWindow::sendDocumentNameToPrinter (QPrinter *printer)
1136 {
1137     QUrl url = d->document->url ();
1138     if (!url.isEmpty ())
1139     {
1140         int dot;
1141 
1142         QString fileName = url.fileName ();
1143         dot = fileName.lastIndexOf ('.');
1144 
1145         // file.ext but not .hidden-file?
1146         if (dot > 0) {
1147             fileName.truncate (dot);
1148         }
1149 
1150     #if DEBUG_KP_MAIN_WINDOW
1151         qCDebug(kpLogMainWindow) << "kpMainWindow::sendDocumentNameToPrinter() fileName="
1152                    << fileName
1153                    << " dir="
1154                    << url.path();
1155     #endif
1156         printer->setDocName (fileName);
1157     }
1158 }
1159 
1160 //--------------------------------------------------------------------------------
1161 
setPrinterPageOrientation(QPrinter * printer)1162 void kpMainWindow::setPrinterPageOrientation(QPrinter *printer)
1163 {
1164     const bool isLandscape = d->document->width() > d->document->height();
1165     printer->setPageOrientation(isLandscape ? QPageLayout::Landscape : QPageLayout::Portrait);
1166 }
1167 
1168 //--------------------------------------------------------------------------------
1169 
sendPreviewToPrinter(QPrinter * printer)1170 void kpMainWindow::sendPreviewToPrinter(QPrinter *printer)
1171 {
1172   sendImageToPrinter(printer, false);
1173 }
1174 
1175 //--------------------------------------------------------------------------------
1176 // private
sendImageToPrinter(QPrinter * printer,bool showPrinterSetupDialog)1177 void kpMainWindow::sendImageToPrinter (QPrinter *printer,
1178         bool showPrinterSetupDialog)
1179 {
1180     // Get image to be printed.
1181     kpImage image = d->document->imageWithSelection ();
1182 
1183 
1184     // Get image DPI.
1185     auto imageDotsPerMeterX = double (d->document->metaInfo ()->dotsPerMeterX ());
1186     auto imageDotsPerMeterY = double (d->document->metaInfo ()->dotsPerMeterY ());
1187 #if DEBUG_KP_MAIN_WINDOW
1188     qCDebug(kpLogMainWindow) << "kpMainWindow::sendImageToPrinter() image:"
1189                << " width=" << image.width ()
1190                << " height=" << image.height ()
1191                << " dotsPerMeterX=" << imageDotsPerMeterX
1192                << " dotsPerMeterY=" << imageDotsPerMeterY;
1193 #endif
1194 
1195     // Image DPI invalid (e.g. new image, could not read from file
1196     // or Qt3 doesn't implement DPI for JPEG)?
1197     if (imageDotsPerMeterX <= 0 || imageDotsPerMeterY <= 0)
1198     {
1199         // Even if just one DPI dimension is invalid, mutate both DPI
1200         // dimensions as we have no information about the intended
1201         // aspect ratio anyway (and other dimension likely to be invalid).
1202 
1203         // When rendering text onto a document, the fonts are rasterised
1204         // according to the screen's DPI.
1205         // TODO: I think we should use the image's DPI.  Technically
1206         //       possible?
1207         //
1208         //       So no matter what computer you draw text on, you get
1209         //       the same pixels.
1210         //
1211         // So we must print at the screen's DPI to get the right text size.
1212         //
1213         // Unfortunately, this means that moving to a different screen DPI
1214         // affects printing.  If you edited the image at a different screen
1215         // DPI than when you print, you get incorrect results.  Furthermore,
1216         // this is bogus if you don't have text in your image.  Worse still,
1217         // what if you have multiple screens connected to the same computer
1218         // with different DPIs?
1219         // TODO: mysteriously, someone else is setting this to 96dpi always.
1220         QPixmap arbitraryScreenElement(1, 1);
1221         const QPaintDevice *screenDevice = &arbitraryScreenElement;
1222         const auto dpiX = screenDevice->logicalDpiX ();
1223         const auto dpiY = screenDevice->logicalDpiY ();
1224     #if DEBUG_KP_MAIN_WINDOW
1225         qCDebug(kpLogMainWindow) << "\tusing screen dpi: x=" << dpiX << " y=" << dpiY;
1226     #endif
1227 
1228         imageDotsPerMeterX = dpiX * KP_INCHES_PER_METER;
1229         imageDotsPerMeterY = dpiY * KP_INCHES_PER_METER;
1230     }
1231 
1232 
1233     // Get page size (excluding margins).
1234     // Coordinate (0,0) is the X here:
1235     //     mmmmm
1236     //     mX  m
1237     //     m   m       m = margin
1238     //     m   m
1239     //     mmmmm
1240     const auto printerWidthMM = printer->widthMM ();
1241     const auto printerHeightMM = printer->heightMM ();
1242 
1243     auto dpiX = imageDotsPerMeterX / KP_INCHES_PER_METER;
1244     auto dpiY = imageDotsPerMeterY / KP_INCHES_PER_METER;
1245 
1246 #if DEBUG_KP_MAIN_WINDOW
1247     qCDebug(kpLogMainWindow) << "\tprinter: widthMM=" << printerWidthMM
1248                << " heightMM=" << printerHeightMM;
1249 
1250     qCDebug(kpLogMainWindow) << "\timage: dpiX=" << dpiX << " dpiY=" << dpiY;
1251 #endif
1252 
1253 
1254     //
1255     // If image doesn't fit on page at intended DPI, change the DPI.
1256     //
1257 
1258     const auto scaleDpiX =
1259         (image.width () / (printerWidthMM / KP_MILLIMETERS_PER_INCH)) / dpiX;
1260     const auto scaleDpiY =
1261         (image.height () / (printerHeightMM / KP_MILLIMETERS_PER_INCH)) / dpiY;
1262     const auto scaleDpi = qMax (scaleDpiX, scaleDpiY);
1263 
1264 #if DEBUG_KP_MAIN_WINDOW
1265     qCDebug(kpLogMainWindow) << "\t\tscaleDpi: x=" << scaleDpiX << " y=" << scaleDpiY
1266                << " --> scale at " << scaleDpi << " to fit?";
1267 #endif
1268 
1269     // Need to increase resolution to fit page?
1270     if (scaleDpi > 1.0)
1271     {
1272         dpiX *= scaleDpi;
1273         dpiY *= scaleDpi;
1274     #if DEBUG_KP_MAIN_WINDOW
1275         qCDebug(kpLogMainWindow) << "\t\t\tto fit page, scaled to:"
1276                    << " dpiX=" << dpiX << " dpiY=" << dpiY;
1277     #endif
1278     }
1279 
1280 
1281     // Make sure DPIs are equal as that's all QPrinter::setResolution()
1282     // supports.  We do this in such a way that we only ever stretch an
1283     // image, to avoid losing information.  Don't antialias as the printer
1284     // will do that to translate our DPI to its physical resolution and
1285     // double-antialiasing looks bad.
1286     if (dpiX > dpiY)
1287     {
1288     #if DEBUG_KP_MAIN_WINDOW
1289         qCDebug(kpLogMainWindow) << "\tdpiX > dpiY; stretching image height to equalise DPIs to dpiX="
1290                    << dpiX;
1291     #endif
1292         kpPixmapFX::scale (&image,
1293              image.width (),
1294              qMax (1, qRound (image.height () * dpiX / dpiY)),
1295              false/*don't antialias*/);
1296 
1297         dpiY = dpiX;
1298     }
1299     else if (dpiY > dpiX)
1300     {
1301     #if DEBUG_KP_MAIN_WINDOW
1302         qCDebug(kpLogMainWindow) << "\tdpiY > dpiX; stretching image width to equalise DPIs to dpiY="
1303                    << dpiY;
1304     #endif
1305         kpPixmapFX::scale (&image,
1306              qMax (1, qRound (image.width () * dpiY / dpiX)),
1307              image.height (),
1308              false/*don't antialias*/);
1309 
1310         dpiX = dpiY;
1311     }
1312 
1313     Q_ASSERT (dpiX == dpiY);
1314 
1315 
1316     // QPrinter::setResolution() has to be called before QPrinter::setup().
1317     printer->setResolution (qMax (1, qRound (dpiX)));
1318 
1319 
1320     sendDocumentNameToPrinter (printer);
1321 
1322 
1323     if (showPrinterSetupDialog)
1324     {
1325         auto *optionsPage = new kpPrintDialogPage (this);
1326         optionsPage->setPrintImageCenteredOnPage (d->configPrintImageCenteredOnPage);
1327 
1328         QPrintDialog printDialog (printer, this);
1329         printDialog.setOptionTabs ({optionsPage});
1330         printDialog.setWindowTitle (i18nc ("@title:window", "Print Image"));
1331 
1332         // Display dialog.
1333         const bool wantToPrint = printDialog.exec ();
1334 
1335         if (optionsPage->printImageCenteredOnPage () !=
1336             d->configPrintImageCenteredOnPage)
1337         {
1338             // Save config option even if the dialog was cancelled.
1339             d->configPrintImageCenteredOnPage = optionsPage->printImageCenteredOnPage ();
1340 
1341             KConfigGroup cfg (KSharedConfig::openConfig (), kpSettingsGroupGeneral);
1342             cfg.writeEntry (kpSettingPrintImageCenteredOnPage,
1343                            d->configPrintImageCenteredOnPage);
1344             cfg.sync ();
1345         }
1346 
1347         if (!wantToPrint) {
1348             return;
1349         }
1350     }
1351 
1352 
1353     // Send image to printer.
1354     QPainter painter;
1355     painter.begin(printer);
1356 
1357     double originX = 0, originY = 0;
1358 
1359     // Center image on page?
1360     if (d->configPrintImageCenteredOnPage)
1361     {
1362         originX = (printer->width() - image.width ()) / 2;
1363         originY = (printer->height() - image.height ()) / 2;
1364     }
1365 
1366     painter.drawImage(qRound(originX), qRound(originY), image);
1367     painter.end();
1368 }
1369 
1370 //---------------------------------------------------------------------
1371 
1372 // private slot
slotPrint()1373 void kpMainWindow::slotPrint ()
1374 {
1375     toolEndShape ();
1376 
1377     QPrinter printer;
1378     setPrinterPageOrientation(&printer);
1379 
1380     sendImageToPrinter (&printer, true/*showPrinterSetupDialog*/);
1381 }
1382 
1383 //---------------------------------------------------------------------
1384 
1385 // private slot
slotPrintPreview()1386 void kpMainWindow::slotPrintPreview ()
1387 {
1388     toolEndShape ();
1389 
1390     QPrinter printer;
1391     setPrinterPageOrientation(&printer);
1392     QPrintPreviewDialog printPreview(&printer, this);
1393     connect(&printPreview, &QPrintPreviewDialog::paintRequested, this, &kpMainWindow::sendPreviewToPrinter);
1394 
1395     printPreview.exec ();
1396 }
1397 
1398 //---------------------------------------------------------------------
1399 
1400 // private slot
slotMail()1401 void kpMainWindow::slotMail ()
1402 {
1403     toolEndShape ();
1404 
1405     if (d->document->url ().isEmpty ()/*no name*/ ||
1406         !(d->document->isFromExistingURL () && d->document->urlExists (d->document->url ())) ||
1407         d->document->isModified ()/*needs to be saved*/)
1408     {
1409         int result = KMessageBox::questionYesNo (this,
1410                         i18n ("You must save this image before sending it.\n"
1411                               "Do you want to save it?"),
1412                         QString(),
1413                         KStandardGuiItem::save (), KStandardGuiItem::cancel ());
1414 
1415         if (result == KMessageBox::Yes)
1416         {
1417             if (!save ())
1418             {
1419                 // save failed or aborted - don't email
1420                 return;
1421             }
1422         }
1423         else
1424         {
1425             // don't want to save - don't email
1426             return;
1427         }
1428     }
1429 
1430     KToolInvocation::invokeMailer (
1431         QString()/*to*/,
1432         QString()/*cc*/,
1433         QString()/*bcc*/,
1434         d->document->prettyFilename()/*subject*/,
1435         QString()/*body*/,
1436         QString()/*messageFile*/,
1437         QStringList(d->document->url().url())/*attachments*/);
1438 }
1439 
1440 //---------------------------------------------------------------------
1441 
1442 // private
queryCloseDocument()1443 bool kpMainWindow::queryCloseDocument ()
1444 {
1445     toolEndShape ();
1446 
1447     if (!d->document || !d->document->isModified ()) {
1448         return true;  // ok to close current doc
1449     }
1450 
1451     int result = KMessageBox::warningYesNoCancel (this,
1452                      i18n ("The document \"%1\" has been modified.\n"
1453                            "Do you want to save it?",
1454                            d->document->prettyFilename ()),
1455                     QString()/*caption*/,
1456                     KStandardGuiItem::save (), KStandardGuiItem::discard ());
1457 
1458     switch (result)
1459     {
1460     case KMessageBox::Yes:
1461         return slotSave ();  // close only if save succeeds
1462     case KMessageBox::No:
1463         return true;  // close without saving
1464     default:
1465         return false;  // don't close current doc
1466     }
1467 }
1468 
1469 //---------------------------------------------------------------------
1470 
1471 // private virtual [base KMainWindow]
queryClose()1472 bool kpMainWindow::queryClose ()
1473 {
1474 #if DEBUG_KP_MAIN_WINDOW
1475     qCDebug(kpLogMainWindow) << "kpMainWindow::queryClose()";
1476 #endif
1477     toolEndShape ();
1478 
1479     if (!queryCloseDocument ()) {
1480         return false;
1481     }
1482 
1483     if (!queryCloseColors ()) {
1484         return false;
1485     }
1486 
1487     return true;
1488 }
1489 
1490 //---------------------------------------------------------------------
1491 
1492 // private slot
slotClose()1493 void kpMainWindow::slotClose ()
1494 {
1495     toolEndShape ();
1496 
1497     if (!queryCloseDocument ()) {
1498         return;
1499     }
1500 
1501     setDocument (nullptr);
1502 }
1503 
1504 //---------------------------------------------------------------------
1505 
1506 // private slot
slotQuit()1507 void kpMainWindow::slotQuit ()
1508 {
1509     toolEndShape ();
1510 
1511     close ();  // will call queryClose()
1512 }
1513 
1514 //---------------------------------------------------------------------
1515