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