diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 966a0b2f13..4d9a511b08 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,1012 +1,1012 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisView.h" #include "KisView_p.h" #include #include #include #include "KoDocumentInfo.h" #include "KoPageLayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "KisReferenceImagesDecoration.h" #include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" #include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , referenceImagesDecoration(new KisReferenceImagesDecoration(_q)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; KisReferenceImagesDecorationSP referenceImagesDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; QMdiSubWindow *subWindow{nullptr}; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg; d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setDrawShadow(false); d->canvasController.setCanvasMode(KoCanvasController::Infinite); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->canvas.addDecoration(d->referenceImagesDecoration); d->referenceImagesDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); } KisView::~KisView() { if (d->viewManager) { if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } d->viewManager->mainWindow()->notifyChildViewDestroyed(this); } KoToolManager::instance()->removeCanvasController(&d->canvasController); d->canvasController.setCanvas(0); KisPart::instance()->removeView(this); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->resourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup); QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); popup.addAction(insertAsReferenceImage); popup.addAction(insertManyLayers); popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); popup.addAction(insertAsReferenceImages); insertAsNewLayer->setEnabled(image() && urls.count() == 1); insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); insertAsReferenceImage->setEnabled(image() && urls.count() == 1); insertManyLayers->setEnabled(image() && urls.count() > 1); insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); insertAsReferenceImages->setEnabled(image() && urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qDebug() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { KisNodeCommandsAdapter adapter(viewManager()); KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); } else if (action == openInNewDocument || action == openManyDocuments) { if (mainWindow()) { mainWindow()->openDocument(url, KisMainWindow::None); } } else if (action == insertAsReferenceImage || action == insertAsReferenceImages) { auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter); reference->setPosition(d->viewConverter.imageToDocument(cursorPos)); d->referenceImagesDecoration->addReferenceImage(reference); KoToolManager::instance()->switchToolRequested("ToolReferenceImages"); } } delete tmp; tmp = 0; } } } } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&, int)), this, SLOT(slotSavingStatusMessage(const QString&, int))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } void KisView::setSubWindow(QMdiSubWindow *subWindow) { d->subWindow = subWindow; } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving) { QStatusBar *sb = statusBar(); if (sb) sb->showMessage(text, timeout); KisConfig cfg; if (sb->isHidden() || (!isAutoSaving && cfg.forceShowSaveMessages()) || (cfg.forceShowAutosaveMessages() && isAutoSaving)) { viewManager()->showFloatingMessage(text, QIcon()); } } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) sb->clearMessage(); } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { - // Check whether we're the last view + // Check whether we're the last (user visible) view int viewCount = KisPart::instance()->viewCount(document()); - if (viewCount > 1) { + if (viewCount > 1 || !isVisible()) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { d->viewManager->statusBar()->setView(0); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; document()->waitForSavingToComplete(); if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { bool isNative = (document()->mimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false)) return false; break; } case QMessageBox::No : { KisImageSP image = document()->image(); image->requestStrokeCancellation(); viewManager()->blockUntilOperationsFinishedForced(image); document()->removeAutoSaveFiles(); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; } default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { QSize oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { QSize newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } void KisView::syncLastActiveNodeToDocument() { KisDocument *doc = document(); if (doc) { doc->setPreActivatedNode(d->currentNode); } } void KisView::saveViewState(KisPropertiesConfiguration &config) const { config.setProperty("file", d->document->url()); config.setProperty("window", mainWindow()->windowStateConfig().name()); if (d->subWindow) { config.setProperty("geometry", d->subWindow->saveGeometry().toBase64()); } config.setProperty("zoomMode", (int)zoomController()->zoomMode()); config.setProperty("zoom", d->zoomManager.zoom()); d->canvasController.saveCanvasState(config); } void KisView::restoreViewState(const KisPropertiesConfiguration &config) { if (d->subWindow) { QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1()); d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry)); } qreal zoom = config.getFloat("zoom", 1.0f); int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE); d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom); d->canvasController.restoreCanvasState(config); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; d->canvas.slotTrySwitchShapeManager(); syncLastActiveNodeToDocument(); } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceManager::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } void KisView::closeView() { d->subWindow->close(); } \ No newline at end of file diff --git a/libs/ui/KisWindowLayoutManager.cpp b/libs/ui/KisWindowLayoutManager.cpp index a32fa1035f..d4a88c7d23 100644 --- a/libs/ui/KisWindowLayoutManager.cpp +++ b/libs/ui/KisWindowLayoutManager.cpp @@ -1,250 +1,252 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisWindowLayoutManager.h" #include #include #include #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisWindowLayoutManager, s_instance) struct KisWindowLayoutManager::Private { bool showImageInAllWindows{false}; bool primaryWorkspaceFollowsFocus{false}; QUuid primaryWindow; QString lastLayoutName; QVector displayLayouts; void loadDisplayLayouts() { KConfigGroup layoutsCfg(KSharedConfig::openConfig(), "DisplayLayouts"); QStringList groups = layoutsCfg.groupList(); Q_FOREACH(QString name, groups) { loadDisplayLayout(name, layoutsCfg.group(name)); } } void loadDisplayLayout(const QString &name, KConfigGroup layoutCfg) { DisplayLayout *layout = new DisplayLayout(); layout->name = name; int displayNumber = 1; while (true) { const QString displayDefinition = layoutCfg.readEntry(QString("Display%1").arg(displayNumber++), QString()); if (displayDefinition.isEmpty()) break; // Just the resolution for now. Later we might want to split by a separator and include things like serial number, etc. const QString &resolutionStr = displayDefinition; QStringList dimensions = resolutionStr.split('x'); if (dimensions.size() != 2) { qWarning() << "Invalid display definition: " << displayDefinition; break; } QSize resolution = QSize( KisDomUtils::toInt(dimensions[0]), KisDomUtils::toInt(dimensions[1]) ); layout->displays.append(Display{resolution}); } layout->preferredWindowLayout = layoutCfg.readEntry("PreferredLayout", ""); displayLayouts.append(layout); } void saveDisplayLayout(const DisplayLayout &layout) { KConfigGroup layoutsCfg(KSharedConfig::openConfig(), "DisplayLayouts"); KConfigGroup layoutCfg = layoutsCfg.group(layout.name); layoutCfg.writeEntry("PreferredLayout", layout.preferredWindowLayout); } }; bool KisWindowLayoutManager::Display::matches(QScreen* screen) const { return resolution == screen->geometry().size(); } bool KisWindowLayoutManager::DisplayLayout::matches(QList screens) const { if (screens.size() != displays.size()) return false; QVector matchedScreens(screens.size()); Q_FOREACH(auto &expectedDisplay, displays) { int i; for (i = 0; i < screens.size(); i++) { if (matchedScreens[i]) continue; if (expectedDisplay.matches(screens[i])) { matchedScreens[i] = true; break; } } if (i == screens.size()) { return false; } } return true; } KisWindowLayoutManager * KisWindowLayoutManager::instance() { return s_instance; } KisWindowLayoutManager::KisWindowLayoutManager() : d(new Private) { d->loadDisplayLayouts(); connect(qobject_cast(KisApplication::instance()), SIGNAL(slotFocusChanged(QWidget*, QWidget*)), this, SLOT(focusChanged(QWidget*, QWidget*))); connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(slotScreensChanged())); connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(slotScreensChanged())); } KisWindowLayoutManager::~KisWindowLayoutManager() { Q_FOREACH(DisplayLayout *layout, d->displayLayouts) { delete layout; } } void KisWindowLayoutManager::setShowImageInAllWindowsEnabled(bool showInAll) { bool wasEnabled = d->showImageInAllWindows; d->showImageInAllWindows = showInAll; if (!wasEnabled && showInAll) { KisMainWindow *currentMainWindow = KisPart::instance()->currentMainwindow(); if (currentMainWindow) { KisView *activeView = currentMainWindow->activeView(); if (activeView) { KisDocument *document = activeView->document(); if (document) { activeDocumentChanged(document); } } } } } bool KisWindowLayoutManager::isShowImageInAllWindowsEnabled() const { return d->showImageInAllWindows; } bool KisWindowLayoutManager::primaryWorkspaceFollowsFocus() const { return d->primaryWorkspaceFollowsFocus; } void KisWindowLayoutManager::setPrimaryWorkspaceFollowsFocus(bool enabled, QUuid primaryWindow) { d->primaryWorkspaceFollowsFocus = enabled; d->primaryWindow = primaryWindow; } QUuid KisWindowLayoutManager::primaryWindowId() const { return d->primaryWindow; } void KisWindowLayoutManager::activeDocumentChanged(KisDocument *document) { if (d->showImageInAllWindows) { Q_FOREACH(QPointer window, KisPart::instance()->mainWindows()) { + if (window->isHidden()) continue; + const auto view = window->activeView(); if (!view || view->document() != document) { window->showDocument(document); } } } } void KisWindowLayoutManager::slotFocusChanged(QWidget *old, QWidget *now) { Q_UNUSED(old); if (!now) return; KisMainWindow *newMainWindow = qobject_cast(now->window()); if (!newMainWindow) return; newMainWindow->windowFocused(); } QString KisWindowLayoutManager::lastLayoutName() { return d->lastLayoutName; } void KisWindowLayoutManager::setLastUsedLayout(const KisWindowLayoutResource *layout) { // For layout selector, accept both window layouts and sessions d->lastLayoutName = layout->name(); // For automatic switching, only allow a window layout proper auto *session = dynamic_cast(layout); if (session) return; QList screens = QGuiApplication::screens(); Q_FOREACH(DisplayLayout *displayLayout, d->displayLayouts) { if (displayLayout->matches(screens)) { displayLayout->preferredWindowLayout = layout->name(); d->saveDisplayLayout(*displayLayout); break; } } } void KisWindowLayoutManager::slotScreensChanged() { QList screens = QGuiApplication::screens(); Q_FOREACH(const DisplayLayout *displayLayout, d->displayLayouts) { if (displayLayout->matches(screens)) { KoResourceServer *windowLayoutServer = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResource *layout = windowLayoutServer->resourceByName(displayLayout->preferredWindowLayout); if (layout) { setLastUsedLayout(layout); layout->applyLayout(); return; } } } } diff --git a/libs/ui/KisWindowLayoutResource.cpp b/libs/ui/KisWindowLayoutResource.cpp index d1a0a5075f..4f85ead731 100644 --- a/libs/ui/KisWindowLayoutResource.cpp +++ b/libs/ui/KisWindowLayoutResource.cpp @@ -1,383 +1,387 @@ /* * Copyright (c) 2018 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisWindowLayoutResource.h" #include "KisWindowLayoutManager.h" #include #include #include #include #include #include #include #include #include #include #include static const int WINDOW_LAYOUT_VERSION = 1; struct KisWindowLayoutResource::Private { struct Window { QUuid windowId; QByteArray geometry; QByteArray windowState; int screen = -1; Qt::WindowStates stateFlags = Qt::WindowNoState; }; QVector windows; bool showImageInAllWindows; bool primaryWorkspaceFollowsFocus; QUuid primaryWindow; Private() = default; explicit Private(QVector windows) : windows(std::move(windows)) {} void openNecessaryWindows(QList> ¤tWindows) { auto *kisPart = KisPart::instance(); Q_FOREACH(const Window &window, windows) { QPointer mainWindow = kisPart->windowById(window.windowId); if (mainWindow.isNull()) { mainWindow = kisPart->createMainWindow(window.windowId); currentWindows.append(mainWindow); mainWindow->show(); } } } void closeUnneededWindows(QList> ¤tWindows) { QVector> windowsToClose; Q_FOREACH(KisMainWindow *mainWindow, currentWindows) { bool keep = false; Q_FOREACH(const Window &window, windows) { if (window.windowId == mainWindow->id()) { keep = true; break; } } if (!keep) { windowsToClose.append(mainWindow); + + // Set the window hidden to prevent "show image in all windows" feature from opening new views on it + // while we migrate views onto the remaining windows + mainWindow->hide(); } } migrateViewsFromClosingWindows(windowsToClose); Q_FOREACH(QPointer mainWindow, windowsToClose) { mainWindow->close(); } } void migrateViewsFromClosingWindows(QVector> &closingWindows) const { auto *kisPart = KisPart::instance(); KisMainWindow *migrationTarget = nullptr; Q_FOREACH(KisMainWindow *mainWindow, kisPart->mainWindows()) { if (!closingWindows.contains(mainWindow)) { migrationTarget = mainWindow; break; } } if (!migrationTarget) { qWarning() << "Problem: window layout with no windows would leave user with zero main windows."; migrationTarget = closingWindows.takeLast(); migrationTarget->show(); } QVector visibleDocuments; Q_FOREACH(KisView *view, kisPart->views()) { KisMainWindow *window = view->mainWindow(); if (!closingWindows.contains(window)) { visibleDocuments.append(view->document()); } } Q_FOREACH(KisDocument *document, kisPart->documents()) { if (!visibleDocuments.contains(document)) { visibleDocuments.append(document); migrationTarget->newView(document); } } } QList getScreensInConsistentOrder() { QList screens = QGuiApplication::screens(); std::sort(screens.begin(), screens.end(), [](const QScreen *a, const QScreen *b) { QRect aRect = a->geometry(); QRect bRect = b->geometry(); if (aRect.y() == bRect.y()) return aRect.x() < bRect.x(); return (aRect.y() < aRect.y()); }); return screens; } }; KisWindowLayoutResource::KisWindowLayoutResource(const QString &filename) : KoResource(filename) , d(new Private) {} KisWindowLayoutResource::~KisWindowLayoutResource() {} KisWindowLayoutResource * KisWindowLayoutResource::fromCurrentWindows( const QString &filename, const QList> &mainWindows, bool showImageInAllWindows, bool primaryWorkspaceFollowsFocus, KisMainWindow *primaryWindow ) { auto resource = new KisWindowLayoutResource(filename); resource->setWindows(mainWindows); resource->d->showImageInAllWindows = showImageInAllWindows; resource->d->primaryWorkspaceFollowsFocus = primaryWorkspaceFollowsFocus; resource->d->primaryWindow = primaryWindow->id(); return resource; } void KisWindowLayoutResource::applyLayout() { auto *kisPart = KisPart::instance(); auto *layoutManager= KisWindowLayoutManager::instance(); layoutManager->setLastUsedLayout(this); QList> currentWindows = kisPart->mainWindows(); if (d->windows.isEmpty()) { // No windows defined (e.g. fresh new session). Leave things as they are, but make sure there's at least one visible main window if (kisPart->mainwindowCount() == 0) { kisPart->createMainWindow(); } else { kisPart->mainWindows().first()->show(); } } else { d->openNecessaryWindows(currentWindows); d->closeUnneededWindows(currentWindows); } // Wait for the windows to finish opening / closing before applying saved geometry. // If we don't, the geometry may get reset after we apply it. QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); Q_FOREACH(const auto &window, d->windows) { QPointer mainWindow = kisPart->windowById(window.windowId); KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow); mainWindow->restoreGeometry(window.geometry); mainWindow->restoreWorkspaceState(window.windowState); } QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QList screens = d->getScreensInConsistentOrder(); Q_FOREACH(const auto &window, d->windows) { if (window.screen >= 0 && window.screen < screens.size()) { QPointer mainWindow = kisPart->windowById(window.windowId); KIS_SAFE_ASSERT_RECOVER_BREAK(mainWindow); QWindow *windowHandle = mainWindow->windowHandle(); if (screens.indexOf(windowHandle->screen()) != window.screen) { QScreen *screen = screens[window.screen]; windowHandle->setScreen(screen); windowHandle->setPosition(screen->availableGeometry().topLeft()); } if (window.stateFlags) { mainWindow->setWindowState(window.stateFlags); } } } layoutManager->setShowImageInAllWindowsEnabled(d->showImageInAllWindows); layoutManager->setPrimaryWorkspaceFollowsFocus(d->primaryWorkspaceFollowsFocus, d->primaryWindow); } bool KisWindowLayoutResource::save() { if (filename().isEmpty()) return false; QFile file(filename()); file.open(QIODevice::WriteOnly); bool res = saveToDevice(&file); file.close(); return res; } bool KisWindowLayoutResource::load() { if (filename().isEmpty()) return false; QFile file(filename()); if (file.size() == 0) return false; if (!file.open(QIODevice::ReadOnly)) { warnKrita << "Can't open file " << filename(); return false; } bool res = loadFromDevice(&file); file.close(); return res; } bool KisWindowLayoutResource::saveToDevice(QIODevice *dev) const { QDomDocument doc; QDomElement root = doc.createElement("WindowLayout"); root.setAttribute("name", name()); root.setAttribute("version", WINDOW_LAYOUT_VERSION); saveXml(doc, root); doc.appendChild(root); QTextStream textStream(dev); textStream.setCodec("UTF-8"); doc.save(textStream, 4); KoResource::saveToDevice(dev); return true; } bool KisWindowLayoutResource::loadFromDevice(QIODevice *dev) { QDomDocument doc; if (!doc.setContent(dev)) { return false; } QDomElement element = doc.documentElement(); setName(element.attribute("name")); d->windows.clear(); loadXml(element); setValid(true); return true; } void KisWindowLayoutResource::saveXml(QDomDocument &doc, QDomElement &root) const { root.setAttribute("showImageInAllWindows", (int)d->showImageInAllWindows); root.setAttribute("primaryWorkspaceFollowsFocus", (int)d->primaryWorkspaceFollowsFocus); root.setAttribute("primaryWindow", d->primaryWindow.toString()); Q_FOREACH(const auto &window, d->windows) { QDomElement elem = doc.createElement("window"); elem.setAttribute("id", window.windowId.toString()); if (window.screen >= 0) { elem.setAttribute("screen", window.screen); } if (window.stateFlags & Qt::WindowMaximized) { elem.setAttribute("maximized", "1"); } QDomElement geometry = doc.createElement("geometry"); geometry.appendChild(doc.createCDATASection(window.geometry.toBase64())); elem.appendChild(geometry); QDomElement state = doc.createElement("windowState"); state.appendChild(doc.createCDATASection(window.windowState.toBase64())); elem.appendChild(state); root.appendChild(elem); } } void KisWindowLayoutResource::loadXml(const QDomElement &element) const { d->showImageInAllWindows = KisDomUtils::toInt(element.attribute("showImageInAllWindows", "0")); d->primaryWorkspaceFollowsFocus = KisDomUtils::toInt(element.attribute("primaryWorkspaceFollowsFocus", "0")); d->primaryWindow = element.attribute("primaryWindow"); for (auto windowElement = element.firstChildElement("window"); !windowElement.isNull(); windowElement = windowElement.nextSiblingElement("window")) { Private::Window window; window.windowId = QUuid(windowElement.attribute("id", QUuid().toString())); if (window.windowId.isNull()) { window.windowId = QUuid::createUuid(); } window.screen = windowElement.attribute("screen", "-1").toInt(); if (windowElement.attribute("maximized", "0") != "0") { window.stateFlags |= Qt::WindowMaximized; } QDomElement geometry = windowElement.firstChildElement("geometry"); QDomElement state = windowElement.firstChildElement("windowState"); window.geometry = QByteArray::fromBase64(geometry.text().toLatin1()); window.windowState = QByteArray::fromBase64(state.text().toLatin1()); d->windows.append(window); } } QString KisWindowLayoutResource::defaultFileExtension() const { return QString(".kwl"); } void KisWindowLayoutResource::setWindows(const QList> &mainWindows) { d->windows.clear(); QList screens = d->getScreensInConsistentOrder(); Q_FOREACH(auto window, mainWindows) { if (!window->isVisible()) continue; Private::Window state; state.windowId = window->id(); state.geometry = window->saveGeometry(); state.windowState = window->saveState(); state.stateFlags = window->windowState(); QWindow *windowHandle = window->windowHandle(); if (windowHandle) { int index = screens.indexOf(windowHandle->screen()); if (index >= 0) { state.screen = index; } } d->windows.append(state); } }