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