
/*
   SPDX-FileCopyrightText: 2003-2007 Clarence Dang <dang@kde.org>

   SPDX-License-Identifier: BSD-2-Clause
*/

#include "kpMainWindow.h"
#include "kpMainWindowPrivate.h"

#include "commands/kpCommandHistory.h"
#include "document/kpDocument.h"
#include "environments/commands/kpCommandEnvironment.h"
#include "environments/document/kpDocumentEnvironment.h"
#include "environments/tools/kpToolEnvironment.h"
#include "generic/kpWidgetMapper.h"
#include "kpThumbnail.h"
#include "kpViewScrollableContainer.h"
#include "layers/selections/image/kpAbstractImageSelection.h"
#include "layers/selections/kpSelectionDrag.h"
#include "tools/kpTool.h"
#include "views/kpZoomedThumbnailView.h"
#include "views/kpZoomedView.h"
#include "views/manager/kpViewManager.h"
#include "widgets/kpColorCells.h"
#include "widgets/toolbars/kpColorToolBar.h"
#include "widgets/toolbars/kpToolToolBar.h"

#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>

#include <QDropEvent>
#include <QMenu>

#include "kpLogCategories.h"

//---------------------------------------------------------------------

kpMainWindow::kpMainWindow()
    : KXmlGuiWindow(nullptr /*parent*/)
{
    init();
    open(QUrl(), true /*create an empty doc*/);

    d->isFullyConstructed = true;
}

//---------------------------------------------------------------------

kpMainWindow::kpMainWindow(const QUrl &url)
    : KXmlGuiWindow(nullptr /*parent*/)
{
    init();
    open(url, true /*create an empty doc with the same url if url !exist*/);

    d->isFullyConstructed = true;
}

//---------------------------------------------------------------------

kpMainWindow::kpMainWindow(kpDocument *newDoc)
    : KXmlGuiWindow(nullptr /*parent*/)
{
    init();
    setDocument(newDoc);

    d->isFullyConstructed = true;
}

//---------------------------------------------------------------------

// TODO: Move into appropriate kpMainWindow_*.cpp or another class

// private
void kpMainWindow::readGeneralSettings()
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName() << ")::readGeneralSettings()";
#endif

    KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral(kpSettingsGroupGeneral));

    d->configFirstTime = cfg.readEntry(kpSettingFirstTime, true);
    d->configShowGrid = cfg.readEntry(kpSettingShowGrid, false);
    d->configShowPath = cfg.readEntry(kpSettingShowPath, false);
    d->moreEffectsDialogLastEffect = cfg.readEntry(kpSettingMoreEffectsLastEffect, 0);
    kpToolEnvironment::drawAntiAliased = cfg.readEntry(kpSettingDrawAntiAliased, true);

    if (cfg.hasKey(kpSettingOpenImagesInSameWindow)) {
        d->configOpenImagesInSameWindow = cfg.readEntry(kpSettingOpenImagesInSameWindow, false);
    } else {
        d->configOpenImagesInSameWindow = false;
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tconfigOpenImagesInSameWindow: first time"
                                 << " - writing default: " << d->configOpenImagesInSameWindow;
#endif
        // TODO: More hidden options have to write themselves out on startup,
        //       not on use, to be discoverable (e.g. printing centered on page).
        cfg.writeEntry(kpSettingOpenImagesInSameWindow, d->configOpenImagesInSameWindow);
        cfg.sync();
    }

    d->configPrintImageCenteredOnPage = cfg.readEntry(kpSettingPrintImageCenteredOnPage, true);

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\t\tGeneral Settings: firstTime=" << d->configFirstTime << " showGrid=" << d->configShowGrid
                             << " showPath=" << d->configShowPath << " moreEffectsDialogLastEffect=" << d->moreEffectsDialogLastEffect
                             << " openImagesInSameWindow=" << d->configOpenImagesInSameWindow
                             << " printImageCenteredOnPage=" << d->configPrintImageCenteredOnPage;
#endif
}

//---------------------------------------------------------------------

// private
void kpMainWindow::readThumbnailSettings()
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tkpMainWindow(" << objectName() << ")::readThumbnailSettings()";
#endif

    KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral(kpSettingsGroupThumbnail));

    d->configThumbnailShown = cfg.readEntry(kpSettingThumbnailShown, false);
    d->configThumbnailGeometry = cfg.readEntry(kpSettingThumbnailGeometry, QRect());
    d->configZoomedThumbnail = cfg.readEntry(kpSettingThumbnailZoomed, true);
    d->configThumbnailShowRectangle = cfg.readEntry(kpSettingThumbnailShowRectangle, true);

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\t\tThumbnail Settings: shown=" << d->configThumbnailShown << " geometry=" << d->configThumbnailGeometry
                             << " zoomed=" << d->configZoomedThumbnail << " showRectangle=" << d->configThumbnailShowRectangle;
#endif
}

//---------------------------------------------------------------------

void kpMainWindow::finalizeGUI(KXMLGUIClient *client)
{
    if (client == this) {
        const QList<QMenu *> menuToHide = findChildren<QMenu *>(QStringLiteral("toolToolBarHiddenMenu"));
        // should only contain one but...
        for (auto *menu : menuToHide) {
            menu->menuAction()->setVisible(false);
        }
    }
}

//---------------------------------------------------------------------

// private
void kpMainWindow::init()
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "kpMainWindow(" << objectName() << ")::init()";
    QTime totalTime;
    totalTime.start();
#endif

    d = new kpMainWindowPrivate;

    //
    // set mainwindow properties
    //

    setMinimumSize(320, 260);
    setAcceptDrops(true);

    //
    // read config
    //

    // KConfig::readEntry() does not actually reread from disk, hence doesn't
    // realize what other processes have done e.g. Settings / Show Path
    KSharedConfig::openConfig()->reparseConfiguration();

    readGeneralSettings();
    readThumbnailSettings();

    //
    // create GUI
    //
    setupActions();
    createStatusBar();
    createGUI();

    createColorBox();
    createToolBox();

    // Let the Tool Box take all the vertical space, since it can be quite
    // tall with all its tool option widgets.  This also avoids occasional
    // bugs like the Tool Box overlapping the Color Tool Bar.
    setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

    // no tabbed docks; does not make sense with only 2 dock widgets
    setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks);

    addDockWidget(Qt::BottomDockWidgetArea, d->colorToolBar, Qt::Horizontal);

    d->scrollView = new kpViewScrollableContainer(this);
    d->scrollView->setObjectName(QStringLiteral("scrollView"));

    connect(d->scrollView, &kpViewScrollableContainer::beganDocResize, this, &kpMainWindow::slotBeganDocResize);

    connect(d->scrollView, &kpViewScrollableContainer::continuedDocResize, this, &kpMainWindow::slotContinuedDocResize);

    connect(d->scrollView, &kpViewScrollableContainer::cancelledDocResize, this, &kpMainWindow::slotCancelledDocResize);

    connect(d->scrollView, &kpViewScrollableContainer::endedDocResize, this, &kpMainWindow::slotEndedDocResize);

    connect(d->scrollView, &kpViewScrollableContainer::statusMessageChanged, this, &kpMainWindow::slotDocResizeMessageChanged);

    connect(d->scrollView, &kpViewScrollableContainer::contentsMoved, this, &kpMainWindow::slotScrollViewAfterScroll);

    setCentralWidget(d->scrollView);

    //
    // set initial pos/size of GUI
    //

    setAutoSaveSettings();

    // our non-XMLGUI tools-toolbar will get initially the toolButtonStyle as
    // all other toolbars, but we want to show only icons for the tools by default
    // (have to do this _after_ setAutoSaveSettings as that applies the default settings)
    if (d->configFirstTime) {
        d->toolToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);

        KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral(kpSettingsGroupGeneral));

        cfg.writeEntry(kpSettingFirstTime, d->configFirstTime = false);
        cfg.sync();
    }

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tall done in " << totalTime.elapsed() << "msec";
#endif
}

//---------------------------------------------------------------------

// private virtual [base KMainWindow]
void kpMainWindow::readProperties(const KConfigGroup &configGroup)
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::readProperties()";
#endif

    // No document at all?
    if (!configGroup.hasKey(kpSessionSettingDocumentUrl)) {
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tno url - no document";
#endif
        setDocument(nullptr);
    }
    // Have a document.
    else {
        const QUrl url = QUrl(configGroup.readEntry(kpSessionSettingDocumentUrl, QString()));
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\turl=" << url;
#endif

        const QSize notFromURLDocSize = configGroup.readEntry(kpSessionSettingNotFromUrlDocumentSize, QSize());

        // Is from URL?
        if (notFromURLDocSize.isEmpty()) {
            // If this fails, the empty document that kpMainWindow::kpMainWindow()
            // created is left untouched.
            openInternal(url, defaultDocSize(), false /*show error message if url !exist*/);
        }
        // Not from URL?
        else {
#if DEBUG_KP_MAIN_WINDOW
            qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << notFromURLDocSize;
#endif
            // Either we have an empty URL or we have a "kolourpaint doesnotexist.png"
            // URL.  Regarding the latter case, if a file now actually exists at that
            // URL, we do open it - ignoring notFromURLDocSize - to avoid putting
            // the user in a situation where he might accidentally overwrite an
            // existing file.
            openInternal(url, notFromURLDocSize, true /*create an empty doc with the same url if url !exist*/);
        }
    }
}

//---------------------------------------------------------------------

// private virtual [base KMainWindow]
// WARNING: KMainWindow API Doc says "No user interaction is allowed
//          in this function!"
void kpMainWindow::saveProperties(KConfigGroup &configGroup)
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "kpMainWindow<" << this << ">::saveProperties()";
#endif

    // No document at all?
    if (!d->document) {
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tno url - no document";
#endif
    }
    // Have a document.
    else {
        // Save URL in all cases:
        //
        //    a) d->document->isFromExistingURL()
        //    b) !d->document->isFromExistingURL() [save size in this case]
        //       i) No URL
        //       ii) URL (from "kolourpaint doesnotexist.png")

        const QUrl url = d->document->url();
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\turl=" << url;
#endif
        configGroup.writeEntry(kpSessionSettingDocumentUrl, url.url());

        // Not from URL e.g. "kolourpaint doesnotexist.png"?
        //
        // Note that "kolourpaint doesexist.png" is considered to be from
        // a URL even if it was deleted in the background. This is because the user expects
        // it to be from a URL, so when we restore the session, we pop up a
        // "cannot find file" dialog, instead of silently creating a new,
        // blank document.
        if (!d->document->isFromExistingURL()) {
            // If we don't have a URL either:
            //
            // a) it was not modified - so we can use either width() or
            //    constructorWidth() (they'll be equal).
            // b) the changes were discarded so we use the initial width,
            //    constructorWidth().
            //
            // Similarly for height() and constructorHeight().
            const QSize docSize(d->document->constructorWidth(), d->document->constructorHeight());
#if DEBUG_KP_MAIN_WINDOW
            qCDebug(kpLogMainWindow) << "\tnot from url; doc size=" << docSize;
#endif
            configGroup.writeEntry(kpSessionSettingNotFromUrlDocumentSize, docSize);
        }
    }
}

//---------------------------------------------------------------------

kpMainWindow::~kpMainWindow()
{
    d->isFullyConstructed = false;

    // Get the kpTool to finish up.  This makes sure that the kpTool destructor
    // will not need to access any other class (that might be deleted before
    // the destructor is called by the QObject child-deletion mechanism).
    if (tool()) {
        tool()->endInternal();
    }

    // Delete document & views.
    // Note: This will disconnects signals from the current kpTool, so kpTool
    //       must not be destructed yet.
    setDocument(nullptr);

    delete d->commandHistory;
    d->commandHistory = nullptr;
    delete d->scrollView;
    d->scrollView = nullptr;

    delete d;
    d = nullptr;
}

//---------------------------------------------------------------------

// public
kpDocument *kpMainWindow::document() const
{
    return d->document;
}

//---------------------------------------------------------------------

// public
kpDocumentEnvironment *kpMainWindow::documentEnvironment()
{
    if (!d->documentEnvironment) {
        d->documentEnvironment = new kpDocumentEnvironment(this);
    }

    return d->documentEnvironment;
}

//---------------------------------------------------------------------

// public
kpViewManager *kpMainWindow::viewManager() const
{
    return d->viewManager;
}

//---------------------------------------------------------------------

// public
kpColorToolBar *kpMainWindow::colorToolBar() const
{
    return d->colorToolBar;
}

//---------------------------------------------------------------------

// public
kpColorCells *kpMainWindow::colorCells() const
{
    return d->colorToolBar ? d->colorToolBar->colorCells() : nullptr;
}

//---------------------------------------------------------------------

// public
kpToolToolBar *kpMainWindow::toolToolBar() const
{
    return d->toolToolBar;
}

//---------------------------------------------------------------------

// public
kpCommandHistory *kpMainWindow::commandHistory() const
{
    return d->commandHistory;
}

//---------------------------------------------------------------------

kpCommandEnvironment *kpMainWindow::commandEnvironment()
{
    if (!d->commandEnvironment) {
        d->commandEnvironment = new kpCommandEnvironment(this);
    }

    return d->commandEnvironment;
}

//---------------------------------------------------------------------

// private
void kpMainWindow::setupActions()
{
    setupFileMenuActions();
    setupEditMenuActions();
    setupViewMenuActions();
    setupImageMenuActions();
    setupColorsMenuActions();
    setupSettingsMenuActions();

    setupTextToolBarActions();
    setupToolActions();
}

//---------------------------------------------------------------------

// private
void kpMainWindow::enableDocumentActions(bool enable)
{
    enableFileMenuDocumentActions(enable);
    enableEditMenuDocumentActions(enable);
    enableViewMenuDocumentActions(enable);
    enableImageMenuDocumentActions(enable);
    enableColorsMenuDocumentActions(enable);
    enableSettingsMenuDocumentActions(enable);
}

//---------------------------------------------------------------------

// private
void kpMainWindow::setDocument(kpDocument *newDoc)
{
    // qCDebug(kpLogMainWindow) << newDoc;

    // is it a close operation?
    if (!newDoc) {
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tdisabling actions";
#endif

        // sync with the bit marked "sync" below

        // TODO: Never disable the Color Box because the user should be
        //       able to manipulate the colors, even without a currently
        //       open document.
        //
        //       We just have to make sure that signals from the Color
        //       Box aren't fired and received unexpectedly when there's
        //       no document.
        Q_ASSERT(d->colorToolBar);
        d->colorToolBar->setEnabled(false);

        enableTextToolBarActions(false);
    }

    // Always disable the tools.
    // If we decide to open a new document/mainView we want
    // kpTool::begin() to be called again e.g. in case it sets the cursor.
    // kpViewManager won't do this because we nuke it to avoid stale state.
    enableToolsDocumentActions(false);

    if (!newDoc) {
        enableDocumentActions(false);
    }

    delete d->mainView;
    d->mainView = nullptr;
    slotDestroyThumbnail();

    // viewManager will die and so will the selection
    d->actionCopy->setEnabled(false);
    d->actionCut->setEnabled(false);
    d->actionDelete->setEnabled(false);
    d->actionDeselect->setEnabled(false);
    d->actionCopyToFile->setEnabled(false);

    delete d->viewManager;
    d->viewManager = nullptr;

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tdestroying document";
    qCDebug(kpLogMainWindow) << "\t\td->document=" << d->document;
#endif
    // destroy current document
    delete d->document;
    d->document = newDoc;

    if (!d->lastCopyToURL.isEmpty()) {
        // remove file name from path
        QString path = d->lastCopyToURL.path();
        path = path.left(path.lastIndexOf(QLatin1Char('/')) + 1);
        d->lastCopyToURL.setPath(path);
    }
    d->copyToFirstTime = true;

    if (!d->lastExportURL.isEmpty()) {
        QString path = d->lastExportURL.path();
        path = path.left(path.lastIndexOf(QLatin1Char('/')) + 1);
        d->lastExportURL.setPath(path);
    }
    d->exportFirstTime = true;

    // not a close operation?
    if (d->document) {
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\treparenting doc that may have been created into a"
                                 << " different mainWindiow";
#endif
        d->document->setEnviron(documentEnvironment());

        d->viewManager = new kpViewManager(this);

        d->mainView = new kpZoomedView(d->document, d->toolToolBar, d->viewManager, nullptr /*buddyView*/, d->scrollView, d->scrollView->viewport());
        d->mainView->setObjectName(QStringLiteral("mainView"));

        d->viewManager->registerView(d->mainView);
        d->scrollView->setView(d->mainView);
        d->mainView->show();

#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\thooking up document signals";
#endif

        // Copy/Cut/Deselect/Delete
        connect(d->document, &kpDocument::selectionEnabled, d->actionCut, &QAction::setEnabled);

        connect(d->document, &kpDocument::selectionEnabled, d->actionCopy, &QAction::setEnabled);

        connect(d->document, &kpDocument::selectionEnabled, d->actionDelete, &QAction::setEnabled);

        connect(d->document, &kpDocument::selectionEnabled, d->actionDeselect, &QAction::setEnabled);

        connect(d->document, &kpDocument::selectionEnabled, d->actionCopyToFile, &QAction::setEnabled);

        // this code won't actually enable any actions at this stage
        // (fresh document) but better safe than sorry
        d->actionCopy->setEnabled(d->document->selection());
        d->actionCut->setEnabled(d->document->selection());
        d->actionDeselect->setEnabled(d->document->selection());
        d->actionDelete->setEnabled(d->document->selection());
        d->actionCopyToFile->setEnabled(d->document->selection());

        connect(d->document, &kpDocument::selectionEnabled, this, &kpMainWindow::slotImageMenuUpdateDueToSelection);

        connect(d->document, &kpDocument::selectionIsTextChanged, this, &kpMainWindow::slotImageMenuUpdateDueToSelection);

        // Status bar
        connect(d->document, &kpDocument::documentOpened, this, &kpMainWindow::recalculateStatusBar);

        connect(d->document, SIGNAL(sizeChanged(QSize)), this, SLOT(setStatusBarDocSize(QSize)));

        // Caption (url, modified)
        connect(d->document, &kpDocument::documentModified, this, &kpMainWindow::slotUpdateCaption);

        connect(d->document, &kpDocument::documentOpened, this, &kpMainWindow::slotUpdateCaption);

        connect(d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotUpdateCaption);

        // File/Reload action only available with non-empty URL
        connect(d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotEnableReload);

        connect(d->document, &kpDocument::documentSaved, this, &kpMainWindow::slotEnableSettingsShowPath);

        // Command history
        Q_ASSERT(d->commandHistory);
        connect(d->commandHistory, &kpCommandHistory::documentRestored, this, &kpMainWindow::slotDocumentRestored); // caption "!modified"

        connect(d->document, &kpDocument::documentSaved, d->commandHistory, &kpCommandHistory::documentSaved);

        // Sync document -> views
        connect(d->document, &kpDocument::contentsChanged, d->viewManager, &kpViewManager::updateViews);

        connect(d->document, static_cast<void (kpDocument::*)(int, int)>(&kpDocument::sizeChanged), d->viewManager, &kpViewManager::adjustViewsToEnvironment);

        connect(d->document, static_cast<void (kpDocument::*)(int, int)>(&kpDocument::sizeChanged), d->viewManager, &kpViewManager::adjustViewsToEnvironment);

#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tenabling actions";
#endif

        // sync with the bit marked "sync" above

        Q_ASSERT(d->colorToolBar);
        d->colorToolBar->setEnabled(true);

        // Hide the text toolbar - it will be shown by kpToolText::begin()
        enableTextToolBarActions(false);

        enableToolsDocumentActions(true);

        enableDocumentActions(true);
    }

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tupdating mainWindow elements";
#endif

    slotImageMenuUpdateDueToSelection();
    recalculateStatusBar();
    slotUpdateCaption(); // Untitled to start with
    slotEnableReload();
    slotEnableSettingsShowPath();

    if (d->commandHistory) {
        d->commandHistory->clear();
    }

#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "\tdocument and views ready to go!";
#endif
}

//---------------------------------------------------------------------

// private virtual [base QWidget]
void kpMainWindow::dragEnterEvent(QDragEnterEvent *e)
{
    // It's faster to test for QMimeData::hasText() first due to the
    // lazy evaluation of the '||' operator.
    e->setAccepted(e->mimeData()->hasText() || e->mimeData()->hasUrls() || kpSelectionDrag::canDecode(e->mimeData()));
}

//---------------------------------------------------------------------

// private virtual [base QWidget]
void kpMainWindow::dropEvent(QDropEvent *e)
{
#if DEBUG_KP_MAIN_WINDOW
    qCDebug(kpLogMainWindow) << "kpMainWindow::dropEvent" << e->pos();
#endif

    QList<QUrl> urls;

    kpAbstractImageSelection *sel = kpSelectionDrag::decode(e->mimeData());
    if (sel) {
        // TODO: How do you actually drop a selection or ordinary images on
        //       the clipboard)?  Will this code path _ever_ execute?
        sel->setTransparency(imageSelectionTransparency());
        // TODO: drop at point like with QTextDrag below?
        paste(*sel);
        delete sel;
    } else if (!(urls = e->mimeData()->urls()).isEmpty()) {
        // LOTODO: kpSetOverrideCursorSaver cursorSaver (Qt::waitCursor);
        //
        //         However, you would need to prefix all possible error/warning
        //         dialogs that might be called, with Qt::arrowCursor e.g. in
        //         kpDocument  and probably a lot more places.
        for (const auto &u : urls)
            open(u);
    } else if (e->mimeData()->hasText()) {
        const QString text = e->mimeData()->text();

        QPoint selTopLeft = KP_INVALID_POINT;
        const QPoint globalPos = QWidget::mapToGlobal(e->pos());
#if DEBUG_KP_MAIN_WINDOW
        qCDebug(kpLogMainWindow) << "\tpos toGlobal=" << globalPos;
#endif

        kpView *view = nullptr;

        if (d->viewManager) {
            view = d->viewManager->viewUnderCursor();
#if DEBUG_KP_MAIN_WINDOW
            qCDebug(kpLogMainWindow) << "\t\tviewUnderCursor=" << view;
#endif
            if (!view) {
                // HACK: see kpViewManager::setViewUnderCursor() to see why
                //       it's not reliable
#if DEBUG_KP_MAIN_WINDOW
                qCDebug(kpLogMainWindow) << "\t\tattempting to discover view";

                if (d->mainView && d->scrollView) {
                    qCDebug(kpLogMainWindow) << "\t\t\tmainView->globalRect=" << kpWidgetMapper::toGlobal(d->mainView, d->mainView->rect())
                                             << " scrollView->globalRect="
                                             << kpWidgetMapper::toGlobal(d->scrollView,
                                                                         QRect(0, 0, d->scrollView->viewport()->width(), d->scrollView->viewport()->height()));
                }
#endif
                if (d->thumbnailView && kpWidgetMapper::toGlobal(d->thumbnailView, d->thumbnailView->rect()).contains(globalPos)) {
                    // TODO: Code will never get executed.
                    //       Thumbnail doesn't accept drops.
                    view = d->thumbnailView;
                } else if (d->mainView && kpWidgetMapper::toGlobal(d->mainView, d->mainView->rect()).contains(globalPos) && d->scrollView
                           && kpWidgetMapper::toGlobal(d->scrollView, QRect(0, 0, d->scrollView->viewport()->width(), d->scrollView->viewport()->height()))
                                  .contains(globalPos)) {
                    view = d->mainView;
                }
            }
        }

        if (view) {
            const QPoint viewPos = view->mapFromGlobal(globalPos);
            const QPoint docPoint = view->transformViewToDoc(viewPos);

            // viewUnderCursor() is hacky and can return a view when we aren't
            // over one thanks to drags.
            if (d->document && d->document->rect().contains(docPoint)) {
                selTopLeft = docPoint;

                // TODO: In terms of doc pixels, would be inconsistent behaviour
                //       based on zoomLevel of view.
                // selTopLeft -= QPoint (-view->selectionResizeHandleAtomicSize (),
                //                       -view->selectionResizeHandleAtomicSize ());
            }
        }

        pasteText(text, true /*force new text selection*/, selTopLeft);
    }
}

//---------------------------------------------------------------------

// private slot
void kpMainWindow::slotScrollViewAfterScroll()
{
    // OPT: Why can't this be moved into kpViewScrollableContainer::slotDragScroll(),
    //      grouping all drag-scroll-related repaints, which would potentially avoid
    //      double repainting?
    if (tool()) {
        tool()->somethingBelowTheCursorChanged();
    }
}

//---------------------------------------------------------------------

// private virtual [base QWidget]
void kpMainWindow::moveEvent(QMoveEvent * /*e*/)
{
    if (d->thumbnail) {
        // Disabled because it lags too far behind the mainWindow
        // d->thumbnail->move (d->thumbnail->pos () + (e->pos () - e->oldPos ()));

        notifyThumbnailGeometryChanged();
    }
}

//---------------------------------------------------------------------

// private slot
void kpMainWindow::slotUpdateCaption()
{
    if (d->document) {
        setCaption(d->configShowPath ? d->document->prettyUrl() : d->document->prettyFilename(), d->document->isModified());
    } else {
        setCaption(QString(), false);
    }
}

//---------------------------------------------------------------------

// private slot
void kpMainWindow::slotDocumentRestored()
{
    if (d->document) {
        d->document->setModified(false);
    }
    slotUpdateCaption();
}

//---------------------------------------------------------------------

#include "moc_kpMainWindow.cpp"
