File indexing completed on 2024-04-28 04:20:19

0001 
0002 /*
0003    Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
0004    All rights reserved.
0005 
0006    Redistribution and use in source and binary forms, with or without
0007    modification, are permitted provided that the following conditions
0008    are met:
0009 
0010    1. Redistributions of source code must retain the above copyright
0011       notice, this list of conditions and the following disclaimer.
0012    2. Redistributions in binary form must reproduce the above copyright
0013       notice, this list of conditions and the following disclaimer in the
0014       documentation and/or other materials provided with the distribution.
0015 
0016    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0017    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0018    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0019    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
0020    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
0021    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0022    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0023    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0024    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
0025    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0026 */
0027 
0028 
0029 #include "kpMainWindow.h"
0030 #include "kpMainWindowPrivate.h"
0031 
0032 #include "layers/selections/image/kpAbstractImageSelection.h"
0033 #include "environments/commands/kpCommandEnvironment.h"
0034 #include "environments/tools/kpToolEnvironment.h"
0035 #include "widgets/kpColorCells.h"
0036 #include "widgets/toolbars/kpColorToolBar.h"
0037 #include "commands/kpCommandHistory.h"
0038 #include "document/kpDocument.h"
0039 #include "environments/document/kpDocumentEnvironment.h"
0040 #include "layers/selections/kpSelectionDrag.h"
0041 #include "kpThumbnail.h"
0042 #include "tools/kpTool.h"
0043 #include "widgets/toolbars/kpToolToolBar.h"
0044 #include "views/manager/kpViewManager.h"
0045 #include "kpViewScrollableContainer.h"
0046 #include "generic/kpWidgetMapper.h"
0047 #include "views/kpZoomedThumbnailView.h"
0048 #include "views/kpZoomedView.h"
0049 
0050 #include <KSharedConfig>
0051 #include <KConfigGroup>
0052 #include <KLocalizedString>
0053 
0054 #include <QMenu>
0055 #include <QDropEvent>
0056 
0057 #include "kpLogCategories.h"
0058 
0059 
0060 //---------------------------------------------------------------------
0061 
0062 kpMainWindow::kpMainWindow ()
0063     : KXmlGuiWindow (nullptr/*parent*/)
0064 {
0065     init ();
0066     open (QUrl (), true/*create an empty doc*/);
0067 
0068     d->isFullyConstructed = true;
0069 }
0070 
0071 //---------------------------------------------------------------------
0072 
0073 kpMainWindow::kpMainWindow (const QUrl &url)
0074     : KXmlGuiWindow (nullptr/*parent*/)
0075 {
0076     init ();
0077     open (url, true/*create an empty doc with the same url if url !exist*/);
0078 
0079     d->isFullyConstructed = true;
0080 }
0081 
0082 //---------------------------------------------------------------------
0083 
0084 kpMainWindow::kpMainWindow (kpDocument *newDoc)
0085     : KXmlGuiWindow (nullptr/*parent*/)
0086 {
0087     init ();
0088     setDocument (newDoc);
0089 
0090     d->isFullyConstructed = true;
0091 }
0092 
0093 //---------------------------------------------------------------------
0094 
0095 
0096 // TODO: Move into appropriate kpMainWindow_*.cpp or another class
0097 
0098 // private
0099 void kpMainWindow::readGeneralSettings ()
0100 {
0101 #if DEBUG_KP_MAIN_WINDOW
0102     qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readGeneralSettings()";
0103 #endif
0104 
0105     KConfigGroup cfg (KSharedConfig::openConfig (), QStringLiteral(kpSettingsGroupGeneral));
0106 
0107     d->configFirstTime = cfg.readEntry (kpSettingFirstTime, true);
0108     d->configShowGrid = cfg.readEntry (kpSettingShowGrid, false);
0109     d->configShowPath = cfg.readEntry (kpSettingShowPath, false);
0110     d->moreEffectsDialogLastEffect = cfg.readEntry (kpSettingMoreEffectsLastEffect, 0);
0111     kpToolEnvironment::drawAntiAliased = cfg.readEntry(kpSettingDrawAntiAliased, true);
0112 
0113     if (cfg.hasKey (kpSettingOpenImagesInSameWindow))
0114     {
0115         d->configOpenImagesInSameWindow = cfg.readEntry (kpSettingOpenImagesInSameWindow, false);
0116     }
0117     else
0118     {
0119         d->configOpenImagesInSameWindow = false;
0120 #if DEBUG_KP_MAIN_WINDOW
0121         qCDebug(kpLogMainWindow) << "\tconfigOpenImagesInSameWindow: first time"
0122                   << " - writing default: " << d->configOpenImagesInSameWindow;
0123 #endif
0124         // TODO: More hidden options have to write themselves out on startup,
0125         //       not on use, to be discoverable (e.g. printing centered on page).
0126         cfg.writeEntry (kpSettingOpenImagesInSameWindow,
0127                         d->configOpenImagesInSameWindow);
0128         cfg.sync ();
0129     }
0130 
0131     d->configPrintImageCenteredOnPage = cfg.readEntry (kpSettingPrintImageCenteredOnPage, true);
0132 
0133 
0134 #if DEBUG_KP_MAIN_WINDOW
0135     qCDebug(kpLogMainWindow) << "\t\tGeneral Settings: firstTime=" << d->configFirstTime
0136                << " showGrid=" << d->configShowGrid
0137                << " showPath=" << d->configShowPath
0138                << " moreEffectsDialogLastEffect=" << d->moreEffectsDialogLastEffect
0139                << " openImagesInSameWindow=" << d->configOpenImagesInSameWindow
0140                << " printImageCenteredOnPage=" << d->configPrintImageCenteredOnPage;
0141 #endif
0142 }
0143 
0144 //---------------------------------------------------------------------
0145 
0146 // private
0147 void kpMainWindow::readThumbnailSettings ()
0148 {
0149 #if DEBUG_KP_MAIN_WINDOW
0150     qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName () << ")::readThumbnailSettings()";
0151 #endif
0152 
0153     KConfigGroup cfg (KSharedConfig::openConfig (), QStringLiteral(kpSettingsGroupThumbnail));
0154 
0155     d->configThumbnailShown = cfg.readEntry (kpSettingThumbnailShown, false);
0156     d->configThumbnailGeometry = cfg.readEntry (kpSettingThumbnailGeometry, QRect ());
0157     d->configZoomedThumbnail = cfg.readEntry (kpSettingThumbnailZoomed, true);
0158     d->configThumbnailShowRectangle = cfg.readEntry (kpSettingThumbnailShowRectangle, true);
0159 
0160 #if DEBUG_KP_MAIN_WINDOW
0161     qCDebug(kpLogMainWindow) << "\t\tThumbnail Settings: shown=" << d->configThumbnailShown
0162                << " geometry=" << d->configThumbnailGeometry
0163                << " zoomed=" << d->configZoomedThumbnail
0164                << " showRectangle=" << d->configThumbnailShowRectangle;
0165 #endif
0166 }
0167 
0168 //---------------------------------------------------------------------
0169 
0170 void kpMainWindow::finalizeGUI(KXMLGUIClient *client)
0171 {
0172   if ( client == this )
0173   {
0174     const QList<QMenu *> menuToHide = findChildren<QMenu *>(QStringLiteral("toolToolBarHiddenMenu"));
0175     // should only contain one but...
0176     for (auto *menu : menuToHide)
0177     {
0178         menu->menuAction()->setVisible(false);
0179     }
0180   }
0181 }
0182 
0183 //---------------------------------------------------------------------
0184 
0185 // private
0186 void kpMainWindow::init ()
0187 {
0188 #if DEBUG_KP_MAIN_WINDOW
0189     qCDebug(kpLogMainWindow) << "kpMainWindow(" << objectName () << ")::init()";
0190     QTime totalTime; totalTime.start ();
0191 #endif
0192 
0193     d = new kpMainWindowPrivate;
0194 
0195     //
0196     // set mainwindow properties
0197     //
0198 
0199     setMinimumSize (320, 260);
0200     setAcceptDrops (true);
0201 
0202     //
0203     // read config
0204     //
0205 
0206     // KConfig::readEntry() does not actually reread from disk, hence doesn't
0207     // realize what other processes have done e.g. Settings / Show Path
0208     KSharedConfig::openConfig ()->reparseConfiguration ();
0209 
0210     readGeneralSettings ();
0211     readThumbnailSettings ();
0212 
0213     //
0214     // create GUI
0215     //
0216     setupActions ();
0217     createStatusBar ();
0218     createGUI ();
0219 
0220     createColorBox ();
0221     createToolBox ();
0222 
0223 
0224     // Let the Tool Box take all the vertical space, since it can be quite
0225     // tall with all its tool option widgets.  This also avoids occasional
0226     // bugs like the Tool Box overlapping the Color Tool Bar.
0227     setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
0228     setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
0229 
0230     // no tabbed docks; does not make sense with only 2 dock widgets
0231     setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks);
0232 
0233     addDockWidget(Qt::BottomDockWidgetArea, d->colorToolBar, Qt::Horizontal);
0234 
0235     d->scrollView = new kpViewScrollableContainer (this);
0236     d->scrollView->setObjectName ( QStringLiteral("scrollView" ));
0237 
0238     connect (d->scrollView, &kpViewScrollableContainer::beganDocResize,
0239              this, &kpMainWindow::slotBeganDocResize);
0240 
0241     connect (d->scrollView, &kpViewScrollableContainer::continuedDocResize,
0242              this, &kpMainWindow::slotContinuedDocResize);
0243 
0244     connect (d->scrollView, &kpViewScrollableContainer::cancelledDocResize,
0245              this, &kpMainWindow::slotCancelledDocResize);
0246 
0247     connect (d->scrollView, &kpViewScrollableContainer::endedDocResize,
0248              this, &kpMainWindow::slotEndedDocResize);
0249 
0250     connect (d->scrollView, &kpViewScrollableContainer::statusMessageChanged,
0251              this, &kpMainWindow::slotDocResizeMessageChanged);
0252 
0253     connect (d->scrollView, &kpViewScrollableContainer::contentsMoved,
0254              this, &kpMainWindow::slotScrollViewAfterScroll);
0255 
0256     setCentralWidget (d->scrollView);
0257 
0258     //
0259     // set initial pos/size of GUI
0260     //
0261 
0262     setAutoSaveSettings ();
0263 
0264     // our non-XMLGUI tools-toolbar will get initially the toolButtonStyle as
0265     // all other toolbars, but we want to show only icons for the tools by default
0266     // (have to do this _after_ setAutoSaveSettings as that applies the default settings)
0267     if (d->configFirstTime)
0268     {
0269       d->toolToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
0270 
0271       KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral(kpSettingsGroupGeneral));
0272 
0273       cfg.writeEntry(kpSettingFirstTime, d->configFirstTime = false);
0274       cfg.sync();
0275     }
0276 
0277 
0278 #if DEBUG_KP_MAIN_WINDOW
0279     qCDebug(kpLogMainWindow) << "\tall done in " << totalTime.elapsed () << "msec";
0280 #endif
0281 }
0282 
0283 //---------------------------------------------------------------------
0284 
0285 // private virtual [base KMainWindow]
0286 void kpMainWindow::readProperties (const KConfigGroup &configGroup)
0287 {
0288 #if DEBUG_KP_MAIN_WINDOW
0289     qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::readProperties()";
0290 #endif
0291 
0292     // No document at all?
0293     if (!configGroup.hasKey (kpSessionSettingDocumentUrl))
0294     {
0295     #if DEBUG_KP_MAIN_WINDOW
0296         qCDebug(kpLogMainWindow) << "\tno url - no document";
0297     #endif
0298         setDocument (nullptr);
0299     }
0300     // Have a document.
0301     else
0302     {
0303         const QUrl url = QUrl (configGroup.readEntry (kpSessionSettingDocumentUrl,
0304                                                 QString ()));
0305     #if DEBUG_KP_MAIN_WINDOW
0306         qCDebug(kpLogMainWindow) << "\turl=" << url;
0307     #endif
0308 
0309         const QSize notFromURLDocSize =
0310             configGroup.readEntry (kpSessionSettingNotFromUrlDocumentSize,
0311                                    QSize ());
0312 
0313         // Is from URL?
0314         if (notFromURLDocSize.isEmpty ())
0315         {
0316             // If this fails, the empty document that kpMainWindow::kpMainWindow()
0317             // created is left untouched.
0318             openInternal (url, defaultDocSize (),
0319                 false/*show error message if url !exist*/);
0320         }
0321         // Not from URL?
0322         else
0323         {
0324         #if DEBUG_KP_MAIN_WINDOW
0325             qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << notFromURLDocSize;
0326         #endif
0327             // Either we have an empty URL or we have a "kolourpaint doesnotexist.png"
0328             // URL.  Regarding the latter case, if a file now actually exists at that
0329             // URL, we do open it - ignoring notFromURLDocSize - to avoid putting
0330             // the user in a situation where he might accidentally overwrite an
0331             // existing file.
0332             openInternal (url, notFromURLDocSize,
0333                 true/*create an empty doc with the same url if url !exist*/);
0334         }
0335     }
0336 
0337 }
0338 
0339 //---------------------------------------------------------------------
0340 
0341 // private virtual [base KMainWindow]
0342 // WARNING: KMainWindow API Doc says "No user interaction is allowed
0343 //          in this function!"
0344 void kpMainWindow::saveProperties (KConfigGroup &configGroup)
0345 {
0346 #if DEBUG_KP_MAIN_WINDOW
0347     qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::saveProperties()";
0348 #endif
0349 
0350     // No document at all?
0351     if (!d->document)
0352     {
0353     #if DEBUG_KP_MAIN_WINDOW
0354         qCDebug(kpLogMainWindow) << "\tno url - no document";
0355     #endif
0356     }
0357     // Have a document.
0358     else
0359     {
0360         // Save URL in all cases:
0361         //
0362         //    a) d->document->isFromExistingURL()
0363         //    b) !d->document->isFromExistingURL() [save size in this case]
0364         //       i) No URL
0365         //       ii) URL (from "kolourpaint doesnotexist.png")
0366 
0367         const QUrl url = d->document->url ();
0368     #if DEBUG_KP_MAIN_WINDOW
0369         qCDebug(kpLogMainWindow) << "\turl=" << url;
0370     #endif
0371         configGroup.writeEntry (kpSessionSettingDocumentUrl, url.url ());
0372 
0373         // Not from URL e.g. "kolourpaint doesnotexist.png"?
0374         //
0375         // Note that "kolourpaint doesexist.png" is considered to be from
0376         // a URL even if it was deleted in the background. This is because the user expects
0377         // it to be from a URL, so when we session restore, we pop up a
0378         // "cannot find file" dialog, instead of silently creating a new,
0379         // blank document.
0380         if (!d->document->isFromExistingURL ())
0381         {
0382             // If we don't have a URL either:
0383             //
0384             // a) it was not modified - so we can use either width() or
0385             //    constructorWidth() (they'll be equal).
0386             // b) the changes were discarded so we use the initial width,
0387             //    constructorWidth().
0388             //
0389             // Similarly for height() and constructorHeight().
0390             const QSize docSize (d->document->constructorWidth (),
0391                                  d->document->constructorHeight ());
0392         #if DEBUG_KP_MAIN_WINDOW
0393             qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << docSize;
0394         #endif
0395             configGroup.writeEntry (kpSessionSettingNotFromUrlDocumentSize, docSize);
0396         }
0397     }
0398 }
0399 
0400 //---------------------------------------------------------------------
0401 
0402 
0403 kpMainWindow::~kpMainWindow ()
0404 {
0405     d->isFullyConstructed = false;
0406 
0407     // Get the kpTool to finish up.  This makes sure that the kpTool destructor
0408     // will not need to access any other class (that might be deleted before
0409     // the destructor is called by the QObject child-deletion mechanism).
0410     if (tool ()) {
0411         tool ()->endInternal ();
0412     }
0413 
0414     // Delete document & views.
0415     // Note: This will disconnects signals from the current kpTool, so kpTool
0416     //       must not be destructed yet.
0417     setDocument (nullptr);
0418 
0419     delete d->commandHistory; d->commandHistory = nullptr;
0420     delete d->scrollView; d->scrollView = nullptr;
0421 
0422     delete d; d = nullptr;
0423 }
0424 
0425 //---------------------------------------------------------------------
0426 
0427 
0428 // public
0429 kpDocument *kpMainWindow::document () const
0430 {
0431     return d->document;
0432 }
0433 
0434 //---------------------------------------------------------------------
0435 
0436 // public
0437 kpDocumentEnvironment *kpMainWindow::documentEnvironment ()
0438 {
0439     if (!d->documentEnvironment) {
0440         d->documentEnvironment = new kpDocumentEnvironment (this);
0441     }
0442 
0443     return d->documentEnvironment;
0444 }
0445 
0446 //---------------------------------------------------------------------
0447 
0448 // public
0449 kpViewManager *kpMainWindow::viewManager () const
0450 {
0451     return d->viewManager;
0452 }
0453 
0454 //---------------------------------------------------------------------
0455 
0456 // public
0457 kpColorToolBar *kpMainWindow::colorToolBar () const
0458 {
0459     return d->colorToolBar;
0460 }
0461 
0462 //---------------------------------------------------------------------
0463 
0464 // public
0465 kpColorCells *kpMainWindow::colorCells () const
0466 {
0467     return d->colorToolBar ? d->colorToolBar->colorCells () : nullptr;
0468 }
0469 
0470 //---------------------------------------------------------------------
0471 
0472 // public
0473 kpToolToolBar *kpMainWindow::toolToolBar () const
0474 {
0475     return d->toolToolBar;
0476 }
0477 
0478 //---------------------------------------------------------------------
0479 
0480 // public
0481 kpCommandHistory *kpMainWindow::commandHistory () const
0482 {
0483     return d->commandHistory;
0484 }
0485 
0486 //---------------------------------------------------------------------
0487 
0488 kpCommandEnvironment *kpMainWindow::commandEnvironment ()
0489 {
0490     if (!d->commandEnvironment) {
0491         d->commandEnvironment = new kpCommandEnvironment (this);
0492     }
0493 
0494     return d->commandEnvironment;
0495 }
0496 
0497 //---------------------------------------------------------------------
0498 
0499 // private
0500 void kpMainWindow::setupActions ()
0501 {
0502     setupFileMenuActions ();
0503     setupEditMenuActions ();
0504     setupViewMenuActions ();
0505     setupImageMenuActions ();
0506     setupColorsMenuActions ();
0507     setupSettingsMenuActions ();
0508 
0509     setupTextToolBarActions ();
0510     setupToolActions ();
0511 }
0512 
0513 //---------------------------------------------------------------------
0514 
0515 // private
0516 void kpMainWindow::enableDocumentActions (bool enable)
0517 {
0518     enableFileMenuDocumentActions (enable);
0519     enableEditMenuDocumentActions (enable);
0520     enableViewMenuDocumentActions (enable);
0521     enableImageMenuDocumentActions (enable);
0522     enableColorsMenuDocumentActions (enable);
0523     enableSettingsMenuDocumentActions (enable);
0524 }
0525 
0526 //---------------------------------------------------------------------
0527 
0528 // private
0529 void kpMainWindow::setDocument (kpDocument *newDoc)
0530 {
0531     //qCDebug(kpLogMainWindow) << newDoc;
0532 
0533     // is it a close operation?
0534     if (!newDoc)
0535     {
0536     #if DEBUG_KP_MAIN_WINDOW
0537         qCDebug(kpLogMainWindow) << "\tdisabling actions";
0538     #endif
0539 
0540         // sync with the bit marked "sync" below
0541 
0542         // TODO: Never disable the Color Box because the user should be
0543         //       able to manipulate the colors, even without a currently
0544         //       open document.
0545         //
0546         //       We just have to make sure that signals from the Color
0547         //       Box aren't fired and received unexpectedly when there's
0548         //       no document.
0549         Q_ASSERT (d->colorToolBar);
0550         d->colorToolBar->setEnabled (false);
0551 
0552         enableTextToolBarActions (false);
0553     }
0554 
0555     // Always disable the tools.
0556     // If we decide to open a new document/mainView we want
0557     // kpTool::begin() to be called again e.g. in case it sets the cursor.
0558     // kpViewManager won't do this because we nuke it to avoid stale state.
0559     enableToolsDocumentActions (false);
0560 
0561     if (!newDoc)
0562     {
0563         enableDocumentActions (false);
0564     }
0565 
0566     delete d->mainView; d->mainView = nullptr;
0567     slotDestroyThumbnail ();
0568 
0569     // viewManager will die and so will the selection
0570     d->actionCopy->setEnabled (false);
0571     d->actionCut->setEnabled (false);
0572     d->actionDelete->setEnabled (false);
0573     d->actionDeselect->setEnabled (false);
0574     d->actionCopyToFile->setEnabled (false);
0575 
0576     delete d->viewManager; d->viewManager = nullptr;
0577 
0578 #if DEBUG_KP_MAIN_WINDOW
0579     qCDebug(kpLogMainWindow) << "\tdestroying document";
0580     qCDebug(kpLogMainWindow) << "\t\td->document=" << d->document;
0581 #endif
0582     // destroy current document
0583     delete d->document;
0584     d->document = newDoc;
0585 
0586 
0587     if (!d->lastCopyToURL.isEmpty ())
0588     {
0589         // remove file name from path
0590         QString path = d->lastCopyToURL.path ();
0591         path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1);
0592         d->lastCopyToURL.setPath (path);
0593     }
0594     d->copyToFirstTime = true;
0595 
0596     if (!d->lastExportURL.isEmpty ())
0597     {
0598         QString path = d->lastExportURL.path ();
0599         path = path.left (path.lastIndexOf (QLatin1Char ('/')) + 1);
0600         d->lastExportURL.setPath (path);
0601     }
0602     d->exportFirstTime = true;
0603 
0604 
0605     // not a close operation?
0606     if (d->document)
0607     {
0608     #if DEBUG_KP_MAIN_WINDOW
0609         qCDebug(kpLogMainWindow) << "\treparenting doc that may have been created into a"
0610                   << " different mainWindiow";
0611     #endif
0612         d->document->setEnviron (documentEnvironment ());
0613 
0614         d->viewManager = new kpViewManager (this);
0615 
0616         d->mainView = new kpZoomedView (d->document, d->toolToolBar, d->viewManager,
0617                                        nullptr/*buddyView*/,
0618                                        d->scrollView,
0619                                        d->scrollView->viewport ());
0620         d->mainView->setObjectName ( QStringLiteral("mainView" ));
0621 
0622         d->viewManager->registerView (d->mainView);
0623         d->scrollView->setView (d->mainView);
0624         d->mainView->show ();
0625 
0626     #if DEBUG_KP_MAIN_WINDOW
0627         qCDebug(kpLogMainWindow) << "\thooking up document signals";
0628     #endif
0629 
0630         // Copy/Cut/Deselect/Delete
0631         connect (d->document, &kpDocument::selectionEnabled,
0632                  d->actionCut, &QAction::setEnabled);
0633 
0634         connect (d->document, &kpDocument::selectionEnabled,
0635                  d->actionCopy, &QAction::setEnabled);
0636 
0637         connect (d->document, &kpDocument::selectionEnabled,
0638                  d->actionDelete, &QAction::setEnabled);
0639 
0640         connect (d->document, &kpDocument::selectionEnabled,
0641                  d->actionDeselect, &QAction::setEnabled);
0642 
0643         connect (d->document, &kpDocument::selectionEnabled,
0644                  d->actionCopyToFile, &QAction::setEnabled);
0645 
0646         // this code won't actually enable any actions at this stage
0647         // (fresh document) but better safe than sorry
0648         d->actionCopy->setEnabled (d->document->selection ());
0649         d->actionCut->setEnabled (d->document->selection ());
0650         d->actionDeselect->setEnabled (d->document->selection ());
0651         d->actionDelete->setEnabled (d->document->selection ());
0652         d->actionCopyToFile->setEnabled (d->document->selection ());
0653 
0654         connect (d->document, &kpDocument::selectionEnabled,
0655                  this, &kpMainWindow::slotImageMenuUpdateDueToSelection);
0656 
0657         connect (d->document, &kpDocument::selectionIsTextChanged,
0658                  this, &kpMainWindow::slotImageMenuUpdateDueToSelection);
0659 
0660         // Status bar
0661         connect (d->document, &kpDocument::documentOpened,
0662                  this, &kpMainWindow::recalculateStatusBar);
0663 
0664         connect (d->document, SIGNAL (sizeChanged(QSize)),
0665                  this, SLOT (setStatusBarDocSize(QSize)));
0666 
0667         // Caption (url, modified)
0668         connect (d->document, &kpDocument::documentModified,
0669                  this, &kpMainWindow::slotUpdateCaption);
0670 
0671         connect (d->document, &kpDocument::documentOpened,
0672                  this, &kpMainWindow::slotUpdateCaption);
0673 
0674         connect (d->document, &kpDocument::documentSaved,
0675                  this, &kpMainWindow::slotUpdateCaption);
0676 
0677         // File/Reload action only available with non-empty URL
0678         connect (d->document, &kpDocument::documentSaved,
0679                  this, &kpMainWindow::slotEnableReload);
0680 
0681         connect (d->document, &kpDocument::documentSaved,
0682                  this, &kpMainWindow::slotEnableSettingsShowPath);
0683 
0684         // Command history
0685         Q_ASSERT (d->commandHistory);
0686         connect (d->commandHistory, &kpCommandHistory::documentRestored,
0687                  this, &kpMainWindow::slotDocumentRestored); // caption "!modified"
0688 
0689         connect (d->document, &kpDocument::documentSaved,
0690                  d->commandHistory, &kpCommandHistory::documentSaved);
0691 
0692         // Sync document -> views
0693         connect (d->document, &kpDocument::contentsChanged,
0694                  d->viewManager, &kpViewManager::updateViews);
0695 
0696         connect (d->document, static_cast<void (kpDocument::*)(int, int)>(&kpDocument::sizeChanged),
0697                  d->viewManager, &kpViewManager::adjustViewsToEnvironment);
0698 
0699         connect (d->document,
0700                  static_cast<void (kpDocument::*)(int, int)>(&kpDocument::sizeChanged),
0701                  d->viewManager, &kpViewManager::adjustViewsToEnvironment);
0702 
0703     #if DEBUG_KP_MAIN_WINDOW
0704         qCDebug(kpLogMainWindow) << "\tenabling actions";
0705     #endif
0706 
0707         // sync with the bit marked "sync" above
0708 
0709         Q_ASSERT (d->colorToolBar);
0710         d->colorToolBar->setEnabled (true);
0711 
0712 
0713         // Hide the text toolbar - it will be shown by kpToolText::begin()
0714         enableTextToolBarActions (false);
0715 
0716         enableToolsDocumentActions (true);
0717 
0718         enableDocumentActions (true);
0719     }
0720 
0721 #if DEBUG_KP_MAIN_WINDOW
0722     qCDebug(kpLogMainWindow) << "\tupdating mainWindow elements";
0723 #endif
0724 
0725     slotImageMenuUpdateDueToSelection ();
0726     recalculateStatusBar ();
0727     slotUpdateCaption ();  // Untitled to start with
0728     slotEnableReload ();
0729     slotEnableSettingsShowPath ();
0730 
0731     if (d->commandHistory) {
0732         d->commandHistory->clear ();
0733     }
0734 
0735 #if DEBUG_KP_MAIN_WINDOW
0736     qCDebug(kpLogMainWindow) << "\tdocument and views ready to go!";
0737 #endif
0738 }
0739 
0740 //---------------------------------------------------------------------
0741 
0742 // private virtual [base QWidget]
0743 void kpMainWindow::dragEnterEvent (QDragEnterEvent *e)
0744 {
0745     // It's faster to test for QMimeData::hasText() first due to the
0746     // lazy evaluation of the '||' operator.
0747     e->setAccepted (e->mimeData ()->hasText () ||
0748                     e->mimeData ()->hasUrls () ||
0749                     kpSelectionDrag::canDecode (e->mimeData ()));
0750 }
0751 
0752 //---------------------------------------------------------------------
0753 
0754 // private virtual [base QWidget]
0755 void kpMainWindow::dropEvent (QDropEvent *e)
0756 {
0757 #if DEBUG_KP_MAIN_WINDOW
0758     qCDebug(kpLogMainWindow) << "kpMainWindow::dropEvent" << e->pos ();
0759 #endif
0760 
0761     QList<QUrl> urls;
0762 
0763     kpAbstractImageSelection *sel = kpSelectionDrag::decode (e->mimeData ());
0764     if (sel)
0765     {
0766         // TODO: How do you actually drop a selection or ordinary images on
0767         //       the clipboard)?  Will this code path _ever_ execute?
0768         sel->setTransparency (imageSelectionTransparency ());
0769         // TODO: drop at point like with QTextDrag below?
0770         paste (*sel);
0771         delete sel;
0772     }
0773     else if (!(urls = e->mimeData ()->urls ()).isEmpty ())
0774     {
0775         // LOTODO: kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor);
0776         //
0777         //         However, you would need to prefix all possible error/warning
0778         //         dialogs that might be called, with Qt::arrowCursor e.g. in
0779         //         kpDocument  and probably a lot more places.
0780         for (const auto &u : urls)
0781             open (u);
0782     }
0783     else if (e->mimeData ()->hasText ())
0784     {
0785         const QString text = e->mimeData ()->text ();
0786 
0787         QPoint selTopLeft = KP_INVALID_POINT;
0788         const QPoint globalPos = QWidget::mapToGlobal (e->pos ());
0789     #if DEBUG_KP_MAIN_WINDOW
0790         qCDebug(kpLogMainWindow) << "\tpos toGlobal=" << globalPos;
0791     #endif
0792 
0793         kpView *view = nullptr;
0794 
0795         if (d->viewManager)
0796         {
0797             view = d->viewManager->viewUnderCursor ();
0798         #if DEBUG_KP_MAIN_WINDOW
0799             qCDebug(kpLogMainWindow) << "\t\tviewUnderCursor=" << view;
0800         #endif
0801             if (!view)
0802             {
0803                 // HACK: see kpViewManager::setViewUnderCursor() to see why
0804                 //       it's not reliable
0805             #if DEBUG_KP_MAIN_WINDOW
0806                 qCDebug(kpLogMainWindow) << "\t\tattempting to discover view";
0807 
0808                 if (d->mainView && d->scrollView)
0809                 {
0810                     qCDebug(kpLogMainWindow) << "\t\t\tmainView->globalRect="
0811                             << kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ())
0812                             << " scrollView->globalRect="
0813                             << kpWidgetMapper::toGlobal (d->scrollView,
0814                                     QRect (0, 0,
0815                                             d->scrollView->viewport()->width (),
0816                                             d->scrollView->viewport()->height ()));
0817                 }
0818             #endif
0819                 if (d->thumbnailView &&
0820                     kpWidgetMapper::toGlobal (d->thumbnailView, d->thumbnailView->rect ())
0821                         .contains (globalPos))
0822                 {
0823                     // TODO: Code will never get executed.
0824                     //       Thumbnail doesn't accept drops.
0825                     view = d->thumbnailView;
0826                 }
0827                 else if (d->mainView &&
0828                          kpWidgetMapper::toGlobal (d->mainView, d->mainView->rect ())
0829                              .contains (globalPos) &&
0830                          d->scrollView &&
0831                          kpWidgetMapper::toGlobal (d->scrollView,
0832                              QRect (0, 0,
0833                                     d->scrollView->viewport()->width (),
0834                                     d->scrollView->viewport()->height ()))
0835                              .contains (globalPos))
0836                 {
0837                     view = d->mainView;
0838                 }
0839             }
0840         }
0841 
0842         if (view)
0843         {
0844             const QPoint viewPos = view->mapFromGlobal (globalPos);
0845             const QPoint docPoint = view->transformViewToDoc (viewPos);
0846 
0847             // viewUnderCursor() is hacky and can return a view when we aren't
0848             // over one thanks to drags.
0849             if (d->document && d->document->rect ().contains (docPoint))
0850             {
0851                 selTopLeft = docPoint;
0852 
0853                 // TODO: In terms of doc pixels, would be inconsistent behaviour
0854                 //       based on zoomLevel of view.
0855                 // selTopLeft -= QPoint (-view->selectionResizeHandleAtomicSize (),
0856                 //                       -view->selectionResizeHandleAtomicSize ());
0857             }
0858         }
0859 
0860         pasteText (text, true/*force new text selection*/, selTopLeft);
0861     }
0862 }
0863 
0864 //---------------------------------------------------------------------
0865 
0866 // private slot
0867 void kpMainWindow::slotScrollViewAfterScroll ()
0868 {
0869     // OPT: Why can't this be moved into kpViewScrollableContainer::slotDragScroll(),
0870     //      grouping all drag-scroll-related repaints, which would potentially avoid
0871     //      double repainting?
0872     if (tool ())
0873     {
0874         tool ()->somethingBelowTheCursorChanged ();
0875     }
0876 }
0877 
0878 //---------------------------------------------------------------------
0879 
0880 // private virtual [base QWidget]
0881 void kpMainWindow::moveEvent (QMoveEvent * /*e*/)
0882 {
0883     if (d->thumbnail)
0884     {
0885         // Disabled because it lags too far behind the mainWindow
0886         // d->thumbnail->move (d->thumbnail->pos () + (e->pos () - e->oldPos ()));
0887 
0888         notifyThumbnailGeometryChanged ();
0889     }
0890 }
0891 
0892 //---------------------------------------------------------------------
0893 
0894 // private slot
0895 void kpMainWindow::slotUpdateCaption ()
0896 {
0897     if (d->document)
0898     {
0899         setCaption (d->configShowPath ? d->document->prettyUrl ()
0900                                      : d->document->prettyFilename (),
0901                     d->document->isModified ());
0902     }
0903     else
0904     {
0905         setCaption (QString(), false);
0906     }
0907 }
0908 
0909 //---------------------------------------------------------------------
0910 
0911 // private slot
0912 void kpMainWindow::slotDocumentRestored ()
0913 {
0914     if (d->document) {
0915         d->document->setModified (false);
0916     }
0917     slotUpdateCaption ();
0918 }
0919 
0920 //---------------------------------------------------------------------
0921 
0922 #include "moc_kpMainWindow.cpp"