diff --git a/krita/data/input/painttoolsaicompatible.profile b/krita/data/input/painttoolsaicompatible.profile
index f63d3f8498..97c999563f 100644
--- a/krita/data/input/painttoolsaicompatible.profile
+++ b/krita/data/input/painttoolsaicompatible.profile
@@ -1,68 +1,68 @@
[Alternate Invocation]
0={4;2;[];2;0;0}
1={2;2;[1000023,1000021];1;0;0}
2={3;2;[1000021,1000023];2;0;0}
3={5;2;[1000021];2;0;0}
4={0;2;[1000021,1000020];1;0;0}
5={1;2;[1000023,1000020];1;0;0}
[Change Primary Setting]
0={0;2;[1000021,1000023];1;0;0}
[Exposure or Gamma]
0={0;2;[59];1;0;0}
[General]
name=Paint Tool Sai Compatible
-version=3
+version=4
[Pan Canvas]
0={4;1;[];0;0;0}
1={3;1;[];0;0;0}
2={2;1;[];0;0;0}
3={1;1;[];0;0;0}
4={0;2;[];4;0;0}
5={0;2;[20];1;0;0}
6={0;4;[];0;0;2}
[Rotate Canvas]
0={3;1;[36];0;0;0}
1={4;1;[1000010];0;0;0}
2={2;1;[1000011];0;0;0}
3={0;2;[1000020];4;0;0}
4={4;2;[1000023,20];2;0;0}
5={0;2;[20,1000023];1;0;0}
6={4;1;[1000006];0;0;0}
[Select Layer]
0={0;2;[52];1;0;0}
[Show Popup Palette]
0={0;2;[1000021,1000023];2;0;0}
[Switch Time]
0={1;1;[1000012];0;0;0}
1={0;1;[1000014];0;0;0}
[Tool Invocation]
0={2;1;[1000000];0;0;0}
1={1;1;[1000004];0;0;0}
2={0;2;[];1;0;0}
3={1;1;[1000005];0;0;0}
4={3;2;[56];1;0;0}
[Zoom Canvas]
0={2;3;[];0;1;0}
1={0;2;[1000021];4;0;0}
10={0;2;[1000021,20];1;0;0}
11={0;4;[];0;0;1}
12={6;1;[33];0;0;0}
13={1;2;[1000021,1000023,20];1;0;0}
2={5;1;[32];0;0;0}
3={2;1;[2b];0;0;0}
4={4;1;[1000010];0;0;0}
5={3;1;[2d];0;0;0}
6={2;1;[3d];0;0;0}
7={3;3;[];0;2;0}
8={2;1;[1000021,20];0;0;0}
9={3;1;[1000021,1000023,20];0;0;0}
diff --git a/krita/data/input/photoshopcompatible.profile b/krita/data/input/photoshopcompatible.profile
index 5ba7902184..f56d232186 100644
--- a/krita/data/input/photoshopcompatible.profile
+++ b/krita/data/input/photoshopcompatible.profile
@@ -1,65 +1,65 @@
[Alternate Invocation]
0={1;2;[1000023,1000020];1;0;0}
1={0;2;[1000021,1000020];1;0;0}
2={5;2;[];0;0;0}
3={3;2;[];0;0;0}
4={2;2;[];0;0;0}
5={4;2;[1000023];1;0;0}
[Change Primary Setting]
0={0;2;[];0;0;0}
[Exposure or Gamma]
0={0;2;[];0;0;0}
[General]
name=Photoshop Compatible
-version=3
+version=4
[Pan Canvas]
0={0;4;[];0;0;2}
1={0;2;[20];1;0;0}
2={0;2;[];4;0;0}
3={1;1;[];0;0;0}
4={2;1;[];0;0;0}
5={3;1;[];0;0;0}
6={4;1;[];0;0;0}
[Rotate Canvas]
0={0;2;[1000020,20];1;0;0}
1={1;2;[1000020,1000023,20];1;0;0}
2={0;2;[1000020];4;0;0}
3={2;1;[34];0;0;0}
4={4;1;[35];0;0;0}
5={3;1;[36];0;0;0}
[Select Layer]
0={0;2;[];0;0;0}
[Show Popup Palette]
0={0;2;[];2;0;0}
[Switch Time]
0={0;1;[1000014];0;0;0}
1={1;1;[1000012];0;0;0}
[Tool Invocation]
0={3;2;[1000020];1;0;0}
1={1;1;[1000005];0;0;0}
2={0;2;[];1;0;0}
3={1;1;[1000004];0;0;0}
4={2;1;[1000000];0;0;0}
[Zoom Canvas]
0={0;2;[1000021];4;0;0}
1={2;3;[];0;1;0}
10={1;2;[1000021,1000023,20];1;0;0}
11={6;1;[];0;0;0}
2={3;3;[];0;2;0}
3={2;1;[3d];0;0;0}
4={3;1;[2d];0;0;0}
5={4;1;[1000021,31];0;0;0}
6={2;1;[2b];0;0;0}
7={5;1;[1000021,30];0;0;0}
8={0;4;[];0;0;1}
9={0;2;[1000021,20];1;0;0}
diff --git a/krita/data/input/tabletpro.profile b/krita/data/input/tabletpro.profile
index 8c5fd77261..14f2cb74f6 100644
--- a/krita/data/input/tabletpro.profile
+++ b/krita/data/input/tabletpro.profile
@@ -1,69 +1,69 @@
[Alternate Invocation]
0={4;2;[1000021];1;0;0}
1={2;2;[1000023,1000021];1;0;0}
2={3;2;[1000021,1000023];2;0;0}
3={5;2;[1000021];2;0;0}
4={0;2;[1000021,1000020];1;0;0}
5={1;2;[1000023,1000020];1;0;0}
[Change Primary Setting]
0={0;2;[1000020];1;0;0}
[Exposure or Gamma]
0={0;2;[59];1;0;0}
[General]
name=Tablet Pro (for use with the Tablet Pro application on Windows)
-version=3
+version=4
[Pan Canvas]
0={4;1;[];0;0;0}
1={3;1;[];0;0;0}
2={2;1;[];0;0;0}
3={1;1;[];0;0;0}
4={0;2;[];4;0;0}
5={0;2;[20];1;0;0}
6={0;4;[];0;0;2}
[Rotate Canvas]
0={3;1;[36];0;0;0}
1={4;1;[35];0;0;0}
2={2;1;[34];0;0;0}
3={0;2;[1000020];4;0;0}
4={1;2;[1000020,1000023,20];1;0;0}
5={0;2;[1000020,20];1;0;0}
6={0;2;[1000039];1;0;0}
[Select Layer]
0={0;2;[52];1;0;0}
1={1;2;[1000020,52];1;0;0}
[Show Popup Palette]
0={0;2;[];2;0;0}
1={0;1;[1000032];0;0;0}
[Switch Time]
0={1;1;[1000012];0;0;0}
1={0;1;[1000014];0;0;0}
[Tool Invocation]
0={2;1;[1000000];0;0;0}
1={1;1;[1000004];0;0;0}
2={0;2;[];1;0;0}
3={1;1;[1000005];0;0;0}
4={3;2;[56];1;0;0}
[Zoom Canvas]
0={0;4;[];0;0;1}
1={0;2;[5a];1;0;0}
10={4;1;[31];0;0;0}
11={5;1;[32];0;0;0}
12={2;1;[2b];0;0;0}
2={3;3;[];0;2;0}
3={2;3;[];0;1;0}
4={0;2;[1000021];4;0;0}
5={6;1;[33];0;0;0}
6={1;2;[1000021,1000023,20];1;0;0}
7={0;2;[1000021,20];1;0;0}
8={3;1;[2d];0;0;0}
9={2;1;[3d];0;0;0}
diff --git a/krita/krita4.xmlgui b/krita/krita4.xmlgui
index ccff54b2da..20cf674f66 100644
--- a/krita/krita4.xmlgui
+++ b/krita/krita4.xmlgui
@@ -1,407 +1,408 @@
&View
+ &Canvas&Snap To&Image&Rotate&LayerNew&Import/ExportImport&Convert&Select&Group&Transform&RotateTransform &All Layers&RotateS&plitS&plit Alpha&SelectSelect &OpaqueFilte&r&ToolsScriptsSetti&ngs&HelpFileBrushes and Stuff
diff --git a/krita/kritamenu.action b/krita/kritamenu.action
index 186fc97e5e..4797708292 100644
--- a/krita/kritamenu.action
+++ b/krita/kritamenu.action
@@ -1,1839 +1,1851 @@
Filedocument-new&NewCreate new documentNew00Ctrl+Nfalsedocument-open&Open...Open an existing documentOpen00Ctrl+Ofalsedocument-open-recentOpen &RecentOpen a document which was recently openedOpen Recent10falsedocument-save&SaveSaveSave10Ctrl+Sfalsedocument-save-asSave &As...Save document under a new nameSave As10Ctrl+Shift+SfalseSessions...Open session managerSessions00falsedocument-importOpen ex&isting Document as Untitled Document...Open existing Document as Untitled DocumentOpen existing Document as Untitled Document00falsedocument-exportE&xport...ExportExport10falseImport animation frames...Import animation framesImport animation frames10false&Render Animation...Render Animation to GIF, Image Sequence or VideoRender Animation10000false&Render Animation AgainRender Animation AgainRender Animation10000falseSave Incremental &VersionSave Incremental VersionSave Incremental Version10Ctrl+Alt+SfalseSave Incremental &BackupSave Incremental BackupSave Incremental Backup10F4false&Create Template From Image...Create Template From ImageCreate Template From Image10falseCreate Copy &From Current ImageCreate Copy From Current ImageCreate Copy From Current Image10falsedocument-print&Print...Print documentPrint10Ctrl+Pfalsedocument-print-previewPrint Previe&wShow a print preview of documentPrint Preview10falseconfigure&Document InformationDocument InformationDocument Information10false&Close AllClose AllClose All10Ctrl+Shift+WfalseC&loseCloseClose10Ctrl+Wfalse&QuitQuit applicationQuit00Ctrl+QfalseEditedit-undoUndoUndo last actionUndo10Ctrl+Zfalseedit-redoRedoRedo last undone actionRedo10Ctrl+Shift+Zfalseedit-cutCu&tCut selection to clipboardCut00Ctrl+Xfalseedit-copy&CopyCopy selection to clipboardCopy00Ctrl+CfalseC&opy (sharp)Copy (sharp)Copy (sharp)1000000000falseCut (&sharp)Cut (sharp)Cut (sharp)1000000000falseCopy &mergedCopy mergedCopy merged1000000000Ctrl+Shift+Cfalseedit-paste&PastePaste clipboard contentPaste00Ctrl+VfalsePaste at CursorPaste at cursorPaste at cursor00Ctrl+Alt+VfalsePaste into &New ImagePaste into New ImagePaste into New Image00Ctrl+Shift+NfalsePaste as R&eference ImagePaste as Reference ImagePaste as Reference Image10Ctrl+Shift+Rfalseedit-clearC&learClearClear10Delfalse&Fill with Foreground ColorFill with Foreground ColorFill with Foreground Color100001Shift+BackspacefalseFill &with Background ColorFill with Background ColorFill with Background Color100001BackspacefalseF&ill with PatternFill with PatternFill with Pattern100001falseFill SpecialFill with Foreground Color (Opacity)Fill with Foreground Color (Opacity)Fill with Foreground Color (Opacity)100001Ctrl+Shift+BackspacefalseFill with Background Color (Opacity)Fill with Background Color (Opacity)Fill with Background Color (Opacity)100001Ctrl+BackspacefalseFill with Pattern (Opacity)Fill with Pattern (Opacity)Fill with Pattern (Opacity)100001falseStro&ke selected shapesStroke selected shapesStroke selected shapes10000000000falseStroke Selec&tion...Stroke selectionStroke selection100000000000falseDelete keyframeDelete keyframeDelete keyframe1000000falseWindowwindow-new&New WindowNew WindowNew Window00falseN&extNextNext100falsePreviousPreviousPreviousfalseViewdocument-new&Show Canvas OnlyShow just the canvas or the whole windowShow Canvas Only00Tabtrueview-fullscreenF&ull Screen ModeDisplay the window in full screenFull Screen Mode00Ctrl+Shift+Ftrue
+
+
+ Detach canvas
+
+ Show the canvas on a separate window
+ Detach canvas
+ 0
+ 0
+
+ true
+
+ &Wrap Around ModeWrap Around ModeWrap Around Mode10true&Instant Preview ModeInstant Preview ModeInstant Preview Mode10Shift+LtrueSoft ProofingTurns on Soft ProofingTurns on Soft ProofingCtrl+YtrueOut of Gamut WarningsTurns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.Ctrl+Shift+Ytruemirror-viewMirror ViewMirror ViewMirror ViewMfalsezoom-original&Reset zoomReset zoomReset zoom10Ctrl+0falsezoom-inZoom &InZoom In00Ctrl++falsezoom-outZoom &OutZoom Out00Ctrl+-falserotate-canvas-rightRotate &Canvas RightRotate Canvas RightRotate Canvas Right10Ctrl+]falserotate-canvas-leftRotate Canvas &LeftRotate Canvas LeftRotate Canvas Left10Ctrl+[falserotation-resetReset Canvas RotationReset Canvas RotationReset Canvas Rotation10falseShow &RulersThe rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. <p>Uncheck this to hide the rulers.</p>Show RulersShow Rulers10trueRulers Track PointerThe rulers will track current mouse position and show it on screen. It can cause suptle performance slowdownRulers Track PointerRulers Track Pointer10trueShow GuidesShow or hide guidesShow Guides10trueLock GuidesLock or unlock guidesLock Guides10trueSnap to GuidesSnap cursor to guides positionSnap to Guides10trueShow Status &BarShow or hide the status barShow Status Bar00trueShow Pixel GridShow Pixel GridShow Pixel Grid10001000trueview-gridShow &GridShow GridShow Grid10000Ctrl+Shift+'trueSnap To GridSnap To GridSnap To Grid1000Ctrl+Shift+;trueShow Snap Options PopupShow Snap Options PopupShow Snap Options Popup1000Shift+sfalseSnap OrthogonalSnap OrthogonalSnap Orthogonal1000trueSnap NodeSnap NodeSnap Node1000trueSnap ExtensionSnap ExtensionSnap Extension1000trueSnap PixelSnap PixelSnap Pixel1000trueSnap IntersectionSnap IntersectionSnap Intersection1000trueSnap Bounding BoxSnap Bounding BoxSnap Bounding Box1000trueSnap Image BoundsSnap Image BoundsSnap Image Bounds1000trueSnap Image CenterSnap Image CenterSnap Image Center1000trueS&how Painting AssistantsShow Painting AssistantsShow Painting Assistants10000trueShow &Assistant PreviewsShow Assistant PreviewsShow Assistant Previews10000trueS&how Reference ImagesShow Reference ImagesShow Reference Images10000trueImagedocument-properties&Properties...PropertiesProperties10000falseformat-stroke-color&Image Background Color and Transparency...Change the background color of the imageImage Background Color and Transparency10000false&Convert Image Color Space...Convert Image Color SpaceConvert Image Color Space10000falsetrim-to-image&Trim to Image SizeTrim to Image SizeTrim to Image Size10falseTrim to Current &LayerTrim to Current LayerTrim to Current Layer1000000falseTrim to S&electionTrim to SelectionTrim to Selection1000000000false&Rotate Image...Rotate ImageRotate Image10000falseobject-rotate-rightRotate &Image 90° to the RightRotate Image 90° to the RightRotate Image 90° to the Right10000falseobject-rotate-leftRotate Image &90° to the LeftRotate Image 90° to the LeftRotate Image 90° to the Left10000falseRotate Image &180°Rotate Image 180°Rotate Image 180°10000false&Shear Image...Shear ImageShear Image10000falsesymmetry-horizontal&Mirror Image HorizontallyMirror Image HorizontallyMirror Image Horizontally10000falsesymmetry-verticalMirror Image &VerticallyMirror Image VerticallyMirror Image Vertically10000falseScale Image To &New Size...Scale Image To New SizeScale Image To New Size10000Ctrl+Alt+Ifalse&Offset Image...Offset ImageOffset Image10000falseR&esize Canvas...Resize CanvasResize Canvas10000Ctrl+Alt+CfalseIm&age Split Image SplitImage Split10000falseSeparate Ima&ge...Separate ImageSeparate Image10000falseSelectedit-select-allSelect &AllSelect AllSelect All00Ctrl+Afalseedit-select-none&DeselectDeselectDeselect11000000000Ctrl+Shift+Afalse&ReselectReselectReselect00Ctrl+Shift+Dfalse&Convert to Vector SelectionConvert to Vector SelectionConvert to Vector Selection1000000000000000000false&Convert to Raster SelectionConvert to Raster SelectionConvert to Raster Selection100000000000000000falseEdit SelectionEdit SelectionEdit Selection10000000000100falseConvert Shapes to &Vector SelectionConvert Shapes to Vector SelectionConvert Shapes to Vector Selection10000000000false&Feather Selection...Feather SelectionFeather Selection10000000000100Shift+F6falseDis&play SelectionDisplay SelectionDisplay Selection10000Ctrl+HtrueSca&le...ScaleScale100000000100falseS&elect from Color Range...Select from Color RangeSelect from Color Range10000100falseSelect &Opaque (Replace)Select OpaqueSelect Opaque1100falseSelect Opaque (&Add)Select Opaque (Add)Select Opaque (Add)1100falseSelect Opaque (&Subtract)Select Opaque (Subtract)Select Opaque (Subtract)1100falseSelect Opaque (&Intersect)Select Opaque (Intersect)Select Opaque (Intersect)1100false&Grow Selection...Grow SelectionGrow Selection10000000000100falseS&hrink Selection...Shrink SelectionShrink Selection10000000000100false&Border Selection...Border SelectionBorder Selection10000000000100falseS&moothSmoothSmooth10000000000100falseFilter&Apply Filter AgainApply Filter AgainApply Filter Again00Ctrl+FfalseAdjustAdjustAdjustfalseArtisticArtisticArtisticfalseBlurBlurBlurfalseColorsColorsColorsfalseEdge DetectionEdge DetectionEdge DetectionfalseEnhanceEnhanceEnhancefalseEmbossEmbossEmbossfalseMapMapMapfalseOtherOtherOtherfalsegmicStart G'MIC-QtStart G'Mic-QtStart G'Mic-QtfalsegmicRe-apply the last G'MIC filterApply the last G'Mic-Qt action againApply the last G'Mic-Qt action againfalseSettingsconfigure&Configure Krita...Configure KritaConfigure Krita00false&Manage Resources...Manage ResourcesManage Resources00falsepreferences-desktop-localeSwitch Application &Language...Switch Application LanguageSwitch Application Languagefalse&Show DockersShow DockersShow Dockers00trueconfigureConfigure Tool&bars...Configure ToolbarsConfigure Toolbars00falseDockersDockersDockersfalse&ThemesThemesThemesfalseim-userActive Author ProfileActive Author ProfileActive Author Profileconfigure-shortcutsConfigure S&hortcuts...Configure ShortcutsConfigure Shortcuts00false&WindowWindowWindowfalseHelphelp-contentsKrita &HandbookKrita HandbookKrita HandbookF1falsetools-report-bug&Report Bug...Report BugReport Bugfalsecalligrakrita&About KritaAbout KritaAbout KritafalsekdeAbout &KDEAbout KDEAbout KDEfalseBrushes and Stuff&GradientsGradientsGradientsfalse&PatternsPatternsPatternsfalse&ColorColorColorfalse&Painter's ToolsPainter's ToolsPainter's ToolsfalseBrush compositeBrush compositeBrush compositefalseBrush option slider 1Brush option slider 1Brush option slider 1falseBrush option slider 2Brush option slider 2Brush option slider 2falseBrush option slider 3Brush option slider 3Brush option slider 3falseMirrorMirrorMirrorfalseLayoutsSelect layoutfalseWorkspacesWorkspacesWorkspacesfalseSearch ActionsWorkspacesWorkspacesfalse
diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp
index 3cda255127..42ada26175 100644
--- a/libs/flake/KoCanvasControllerWidget.cpp
+++ b/libs/flake/KoCanvasControllerWidget.cpp
@@ -1,626 +1,615 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2008-2009 Thomas Zander
* Copyright (C) 2006 Peter Simonsson
* Copyright (C) 2006, 2009 Thorsten Zachmann
* Copyright (C) 2007-2010 Boudewijn Rempt
* Copyright (C) 2007 C. Boemann
* Copyright (C) 2006-2008 Jan Hambrecht
*
* 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 "KoCanvasControllerWidget.h"
#include "KoCanvasControllerWidget_p.h"
#include "KoCanvasControllerWidgetViewport_p.h"
#include "KoShape.h"
#include "KoViewConverter.h"
#include "KoCanvasBase.h"
#include "KoCanvasObserverBase.h"
#include "KoCanvasSupervisor.h"
#include "KoToolManager_p.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void KoCanvasControllerWidget::Private::setDocumentOffset()
{
// The margins scroll the canvas widget inside the viewport, not
// the document. The documentOffset is meant to be the value that
// the canvas must add to the update rect in its paint event, to
// compensate.
QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value());
q->proxyObject->emitMoveDocumentOffset(pt);
QWidget *canvasWidget = canvas->canvasWidget();
if (canvasWidget) {
// If it isn't an OpenGL canvas
if (qobject_cast(canvasWidget) == 0) {
QPoint diff = q->documentOffset() - pt;
canvasWidget->scroll(diff.x(), diff.y(), canvasWidget->rect());
}
}
q->setDocumentOffset(pt);
}
void KoCanvasControllerWidget::Private::resetScrollBars()
{
// The scrollbar value always points at the top-left corner of the
// bit of image we paint.
int docH = (int)q->documentSize().height() + q->margin();
int docW = (int)q->documentSize().width() + q->margin();
int drawH = viewportWidget->height();
int drawW = viewportWidget->width();
QScrollBar *hScroll = q->horizontalScrollBar();
QScrollBar *vScroll = q->verticalScrollBar();
int horizontalReserve = vastScrollingFactor * drawW;
int verticalReserve = vastScrollingFactor * drawH;
int xMin = -horizontalReserve;
int yMin = -verticalReserve;
int xMax = docW - drawW + horizontalReserve;
int yMax = docH - drawH + verticalReserve;
hScroll->setRange(xMin, xMax);
vScroll->setRange(yMin, yMax);
int fontheight = QFontMetrics(q->font()).height();
vScroll->setPageStep(drawH);
vScroll->setSingleStep(fontheight);
hScroll->setPageStep(drawW);
hScroll->setSingleStep(fontheight);
}
void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event)
{
if (!canvas) return;
if (!canvas->viewConverter()) return;
QPoint pointerPos;
QMouseEvent *mouseEvent = dynamic_cast(event);
if (mouseEvent) {
pointerPos = mouseEvent->pos();
} else {
QTabletEvent *tabletEvent = dynamic_cast(event);
if (tabletEvent) {
pointerPos = tabletEvent->pos();
}
}
QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset();
QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
#include
void KoCanvasControllerWidget::Private::activate()
{
- QWidget *parent = q;
- while (parent->parentWidget()) {
- parent = parent->parentWidget();
- }
- KoCanvasSupervisor *observerProvider = dynamic_cast(parent);
if (!observerProvider) {
return;
}
-
KoCanvasBase *canvas = q->canvas();
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observer->setObservedCanvas(canvas);
}
}
}
void KoCanvasControllerWidget::Private::unsetCanvas()
{
- QWidget *parent = q;
- while (parent->parentWidget()) {
- parent = parent->parentWidget();
- }
- KoCanvasSupervisor *observerProvider = dynamic_cast(parent);
if (!observerProvider) {
return;
}
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
if (observer->observedCanvas() == q->canvas()) {
observer->unsetObservedCanvas();
}
}
}
}
////////////
-KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent)
+KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, KoCanvasSupervisor *observerProvider, QWidget *parent)
: QAbstractScrollArea(parent)
, KoCanvasController(actionCollection)
- , d(new Private(this))
+ , d(new Private(this, observerProvider))
{
// We need to set this as QDeclarativeView sets them a bit different from QAbstractScrollArea
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// And then our own Viewport
d->viewportWidget = new Viewport(this);
setViewport(d->viewportWidget);
d->viewportWidget->setFocusPolicy(Qt::NoFocus);
setFocusPolicy(Qt::NoFocus);
setFrameStyle(0);
//setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setAutoFillBackground(false);
/*
Fixes: apps starting at zero zoom.
Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose
to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the
widget means we no longer get rounding errors in zooming and we no longer end up with zero-zoom.
Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible
*/
setMinimumSize(QSize(50, 50));
setMouseTracking(true);
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX()));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY()));
connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX()));
connect(proxyObject, SIGNAL(moveDocumentOffset(QPoint)), d->viewportWidget, SLOT(documentOffsetMoved(QPoint)));
}
KoCanvasControllerWidget::~KoCanvasControllerWidget()
{
delete d;
}
void KoCanvasControllerWidget::activate()
{
d->activate();
}
void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy)
{
Q_UNUSED(dx);
Q_UNUSED(dy);
d->setDocumentOffset();
}
QSizeF KoCanvasControllerWidget::viewportSize() const
{
// Calculate viewport size aligned to device pixels to match KisOpenGLCanvas2.
qreal dpr = viewport()->devicePixelRatioF();
int viewportWidth = static_cast(viewport()->width() * dpr);
int viewportHeight = static_cast(viewport()->height() * dpr);
return QSizeF(viewportWidth / dpr, viewportHeight / dpr);
}
void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent)
{
proxyObject->emitSizeChanged(resizeEvent->size());
// XXX: When resizing, keep the area we're looking at now in the
// center of the resized view.
resetScrollBars();
d->setDocumentOffset();
}
void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas)
{
if (d->canvas) {
d->unsetCanvas();
proxyObject->emitCanvasRemoved(this);
d->canvas->setCanvasController(0);
d->canvas->canvasWidget()->removeEventFilter(this);
}
d->canvas = canvas;
if (d->canvas) {
d->canvas->setCanvasController(this);
changeCanvasWidget(d->canvas->canvasWidget());
proxyObject->emitCanvasSet(this);
QTimer::singleShot(0, this, SLOT(activate()));
setPreferredCenterFractionX(0);
setPreferredCenterFractionY(0);
}
}
KoCanvasBase* KoCanvasControllerWidget::canvas() const
{
if (d->canvas.isNull()) return 0;
return d->canvas;
}
void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget)
{
if (d->viewportWidget->canvas()) {
widget->setCursor(d->viewportWidget->canvas()->cursor());
d->viewportWidget->canvas()->removeEventFilter(this);
}
d->viewportWidget->setCanvas(widget);
setFocusProxy(d->canvas->canvasWidget());
}
int KoCanvasControllerWidget::visibleHeight() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int height1;
if (canvasWidget == 0)
height1 = viewport()->height();
else
height1 = qMin(viewport()->height(), canvasWidget->height());
int height2 = height();
return qMin(height1, height2);
}
int KoCanvasControllerWidget::visibleWidth() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int width1;
if (canvasWidget == 0)
width1 = viewport()->width();
else
width1 = qMin(viewport()->width(), canvasWidget->width());
int width2 = width();
return qMin(width1, width2);
}
int KoCanvasControllerWidget::canvasOffsetX() const
{
int offset = -horizontalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->x() + frameWidth();
}
return offset;
}
int KoCanvasControllerWidget::canvasOffsetY() const
{
int offset = -verticalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->y() + frameWidth();
}
return offset;
}
void KoCanvasControllerWidget::updateCanvasOffsetX()
{
proxyObject->emitCanvasOffsetXChanged(canvasOffsetX());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionX((horizontalScrollBar()->value()
+ viewport()->width() / 2.0) / documentSize().width());
}
void KoCanvasControllerWidget::updateCanvasOffsetY()
{
proxyObject->emitCanvasOffsetYChanged(canvasOffsetY());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionY((verticalScrollBar()->value()
+ verticalScrollBar()->pageStep() / 2.0) / documentSize().height());
}
void KoCanvasControllerWidget::ensureVisible(KoShape *shape)
{
Q_ASSERT(shape);
ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect()));
}
void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth)
{
QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight());
QRect viewRect = rect.toRect();
viewRect.translate(d->canvas->documentOrigin());
if (!viewRect.isValid() || currentVisible.contains(viewRect))
return; // its visible. Nothing to do.
// if we move, we move a little more so the amount of times we have to move is less.
int jumpWidth = smooth ? 0 : currentVisible.width() / 5;
int jumpHeight = smooth ? 0 : currentVisible.height() / 5;
if (!smooth && viewRect.width() + jumpWidth > currentVisible.width())
jumpWidth = 0;
if (!smooth && viewRect.height() + jumpHeight > currentVisible.height())
jumpHeight = 0;
int horizontalMove = 0;
if (currentVisible.width() <= viewRect.width()) // center view
horizontalMove = viewRect.center().x() - currentVisible.center().x();
else if (currentVisible.x() > viewRect.x()) // move left
horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth;
else if (currentVisible.right() < viewRect.right()) // move right
horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth);
int verticalMove = 0;
if (currentVisible.height() <= viewRect.height()) // center view
verticalMove = viewRect.center().y() - currentVisible.center().y();
if (currentVisible.y() > viewRect.y()) // move up
verticalMove = viewRect.y() - currentVisible.y() - jumpHeight;
else if (currentVisible.bottom() < viewRect.bottom()) // move down
verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight);
pan(QPoint(horizontalMove, verticalMove));
}
void KoCanvasControllerWidget::recenterPreferred()
{
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
QPointF center = preferredCenter();
// convert into a viewport based point
center.rx() += d->canvas->canvasWidget()->x() + frameWidth();
center.ry() += d->canvas->canvasWidget()->y() + frameWidth();
// scroll to a new center point
QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height());
setScrollBarValue(topLeft.toPoint());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomIn(const QPoint ¢er)
{
zoomBy(center, sqrt(2.0));
}
void KoCanvasControllerWidget::zoomOut(const QPoint ¢er)
{
zoomBy(center, sqrt(0.5));
}
void KoCanvasControllerWidget::zoomBy(const QPoint ¢er, qreal zoom)
{
setPreferredCenterFractionX(1.0 * center.x() / documentSize().width());
setPreferredCenterFractionY(1.0 * center.y() / documentSize().height());
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoom, preferredCenter());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomTo(const QRect &viewRect)
{
qreal scale;
if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height())
scale = 1.0 * viewport()->height() / viewRect.height();
else
scale = 1.0 * viewport()->width() / viewRect.width();
zoomBy(viewRect.center(), scale);
}
void KoCanvasControllerWidget::updateDocumentSize(const QSizeF &sz, bool recalculateCenter)
{
// Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates.
if (KoCanvasController::documentSize() == sz)
return;
if (!recalculateCenter) {
// assume the distance from the top stays equal and recalculate the center.
setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width());
setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height());
}
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
KoCanvasController::setDocumentSize(sz);
d->viewportWidget->setDocumentSize(sz);
resetScrollBars();
// Always emit the new offset.
updateCanvasOffsetX();
updateCanvasOffsetY();
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::setZoomWithWheel(bool zoom)
{
d->zoomWithWheel = zoom;
}
void KoCanvasControllerWidget::setVastScrolling(qreal factor)
{
d->vastScrollingFactor = factor;
}
QPointF KoCanvasControllerWidget::currentCursorPosition() const
{
QWidget *canvasWidget = d->canvas->canvasWidget();
const KoViewConverter *converter = d->canvas->viewConverter();
return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos());
}
void KoCanvasControllerWidget::pan(const QPoint &distance)
{
QPoint sourcePoint = scrollBarValue();
setScrollBarValue(sourcePoint + distance);
}
void KoCanvasControllerWidget::panUp()
{
pan(QPoint(0, verticalScrollBar()->singleStep()));
}
void KoCanvasControllerWidget::panDown()
{
pan(QPoint(0, -verticalScrollBar()->singleStep()));
}
void KoCanvasControllerWidget::panLeft()
{
pan(QPoint(horizontalScrollBar()->singleStep(), 0));
}
void KoCanvasControllerWidget::panRight()
{
pan(QPoint(-horizontalScrollBar()->singleStep(), 0));
}
void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint)
{
setPreferredCenterFractionX(viewPoint.x() / documentSize().width());
setPreferredCenterFractionY(viewPoint.y() / documentSize().height());
recenterPreferred();
}
QPointF KoCanvasControllerWidget::preferredCenter() const
{
QPointF center;
center.setX(preferredCenterFractionX() * documentSize().width());
center.setY(preferredCenterFractionY() * documentSize().height());
return center;
}
void KoCanvasControllerWidget::paintEvent(QPaintEvent *event)
{
QPainter gc(viewport());
d->viewportWidget->handlePaintEvent(gc, event);
}
void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event)
{
d->viewportWidget->handleDragEnterEvent(event);
}
void KoCanvasControllerWidget::dropEvent(QDropEvent *event)
{
d->viewportWidget->handleDropEvent(event);
}
void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event)
{
d->viewportWidget->handleDragMoveEvent(event);
}
void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event)
{
d->viewportWidget->handleDragLeaveEvent(event);
}
void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event)
{
if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) {
const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5);
zoomRelativeToPoint(event->pos(), zoomCoeff);
event->accept();
} else
QAbstractScrollArea::wheelEvent(event);
}
void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff)
{
const QPoint offset = scrollBarValue();
const QPoint mousePos(widgetPoint + offset);
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoomCoeff, mousePos);
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
bool KoCanvasControllerWidget::focusNextPrevChild(bool)
{
// we always return false meaning the canvas takes keyboard focus, but never gives it away.
return false;
}
void KoCanvasControllerWidget::setMargin(int margin)
{
KoCanvasController::setMargin(margin);
Q_ASSERT(d->viewportWidget);
d->viewportWidget->setMargin(margin);
}
QPoint KoCanvasControllerWidget::scrollBarValue() const
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
return QPoint(hBar->value(), vBar->value());
}
void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value)
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
hBar->setValue(value.x());
vBar->setValue(value.y());
}
void KoCanvasControllerWidget::resetScrollBars()
{
d->resetScrollBars();
}
qreal KoCanvasControllerWidget::vastScrollingFactor() const
{
return d->vastScrollingFactor;
}
KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv()
{
return d;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoCanvasControllerWidget.cpp"
diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h
index a74e5f109b..a3494227d5 100644
--- a/libs/flake/KoCanvasControllerWidget.h
+++ b/libs/flake/KoCanvasControllerWidget.h
@@ -1,193 +1,193 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2008 Thomas Zander
* Copyright (C) 2007-2010 Boudewijn Rempt
* Copyright (C) 2007-2008 C. Boemann
* Copyright (C) 2006-2007 Jan Hambrecht
* Copyright (C) 2009 Thorsten Zachmann
*
* 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.
*/
#ifndef KOCANVASCONTROLLERWIDGET_H
#define KOCANVASCONTROLLERWIDGET_H
#include "kritaflake_export.h"
#include
#include
#include "KoCanvasController.h"
class KoShape;
class KoCanvasBase;
-
+class KoCanvasSupervisor;
/**
* KoCanvasController implementation for QWidget based canvases
*/
class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController
{
Q_OBJECT
public:
/**
* Constructor.
* @param actionCollection the action collection for this widget
* @param parent the parent this widget will belong to
*/
- explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0);
+ explicit KoCanvasControllerWidget(KActionCollection * actionCollection, KoCanvasSupervisor *observerProvider, QWidget *parent = 0);
~KoCanvasControllerWidget() override;
/**
* Reimplemented from QAbstractScrollArea.
*/
void scrollContentsBy(int dx, int dy) override;
QSizeF viewportSize() const override;
/// Reimplemented from KoCanvasController
/**
* Activate this canvascontroller
*/
virtual void activate();
void setCanvas(KoCanvasBase *canvas) override;
KoCanvasBase *canvas() const override;
/**
* Change the actual canvas widget used by the current canvas. This allows the canvas widget
* to be changed while keeping the current KoCanvasBase canvas and its associated resources as
* they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas.
* @param widget the new canvas widget.
*/
virtual void changeCanvasWidget(QWidget *widget);
int visibleHeight() const override;
int visibleWidth() const override;
int canvasOffsetX() const override;
int canvasOffsetY() const override;
void ensureVisible(const QRectF &rect, bool smooth = false) override;
void ensureVisible(KoShape *shape) override;
/**
* will cause the toolOptionWidgetsChanged to be emitted and all
* listeners to be updated to the new widget.
*
* FIXME: This doesn't belong her and it does an
* inherits("KoView") so it too much tied to komain
*
* @param widgets the map of widgets
*/
void setToolOptionWidgets(const QList > &widgets);
void zoomIn(const QPoint ¢er) override;
void zoomOut(const QPoint ¢er) override;
void zoomBy(const QPoint ¢er, qreal zoom) override;
void zoomTo(const QRect &rect) override;
/**
* Zoom document keeping point \p widgetPoint unchanged
* \param widgetPoint sticky point in widget pixels
* \param zoomCoeff the zoom
*/
virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff);
void recenterPreferred() override;
void setPreferredCenter(const QPointF &viewPoint) override;
/// Returns the currently set preferred center point in view coordinates (pixels)
QPointF preferredCenter() const override;
void pan(const QPoint &distance) override;
virtual void panUp() override;
virtual void panDown() override;
virtual void panLeft() override;
virtual void panRight() override;
void setMargin(int margin) override;
QPoint scrollBarValue() const override;
/**
* Used by KisCanvasController to correct the scrollbars position
* after the rotation.
*/
void setScrollBarValue(const QPoint &value) override;
void updateDocumentSize(const QSizeF &sz, bool recalculateCenter = true) override;
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
void setZoomWithWheel(bool zoom) override;
void setVastScrolling(qreal factor) override;
QPointF currentCursorPosition() const override;
void resetScrollBars() override;
/**
* \internal
*/
class Private;
KoCanvasControllerWidget::Private *priv();
private Q_SLOTS:
/// Called by the horizontal scrollbar when its value changes
void updateCanvasOffsetX();
/// Called by the vertical scrollbar when its value changes
void updateCanvasOffsetY();
protected:
friend class KisZoomAndPanTest;
qreal vastScrollingFactor() const;
/// reimplemented from QWidget
void paintEvent(QPaintEvent *event) override;
/// reimplemented from QWidget
void resizeEvent(QResizeEvent *resizeEvent) override;
/// reimplemented from QWidget
void dragEnterEvent(QDragEnterEvent *event) override;
/// reimplemented from QWidget
void dropEvent(QDropEvent *event) override;
/// reimplemented from QWidget
void dragMoveEvent(QDragMoveEvent *event) override;
/// reimplemented from QWidget
void dragLeaveEvent(QDragLeaveEvent *event) override;
/// reimplemented from QWidget
void wheelEvent(QWheelEvent *event) override;
/// reimplemented from QWidget
bool focusNextPrevChild(bool next) override;
private:
Q_PRIVATE_SLOT(d, void activate())
Private * const d;
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidget_p.h b/libs/flake/KoCanvasControllerWidget_p.h
index d111af73a1..cfb277c19f 100644
--- a/libs/flake/KoCanvasControllerWidget_p.h
+++ b/libs/flake/KoCanvasControllerWidget_p.h
@@ -1,68 +1,71 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2008-2009 Thomas Zander
* Copyright (C) 2006 Peter Simonsson
* Copyright (C) 2006, 2009 Thorsten Zachmann
* Copyright (C) 2007-2010 Boudewijn Rempt
* Copyright (C) 2007 C. Boemann
* Copyright (C) 2006-2008 Jan Hambrecht
*
* 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.
*/
#ifndef KoCanvasControllerWidget_p_h
#define KoCanvasControllerWidget_p_h
#include
#include
+#include "KoCanvasSupervisor.h"
class KoCanvasControllerWidget;
class Viewport;
class KoCanvasBase;
class Q_DECL_HIDDEN KoCanvasControllerWidget::Private
{
public:
- Private(KoCanvasControllerWidget *qq)
+ Private(KoCanvasControllerWidget *qq, KoCanvasSupervisor *observerProvider)
: q(qq)
+ , observerProvider(observerProvider)
, canvas(0)
, ignoreScrollSignals(false)
, zoomWithWheel(false)
, vastScrollingFactor(0)
{
}
/**
* Gets called by the tool manager if this canvas controller is the current active canvas controller.
*/
void setDocumentOffset();
void resetScrollBars();
void emitPointerPositionChangedSignals(QEvent *event);
void activate();
void unsetCanvas();
KoCanvasControllerWidget *q;
+ KoCanvasSupervisor *observerProvider;
QPointer canvas;
Viewport *viewportWidget;
bool ignoreScrollSignals;
bool zoomWithWheel;
qreal vastScrollingFactor;
};
#endif
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index 06c34d8cbd..068cfdf3a9 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,2062 +1,2067 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2007 Boudewijn Rempt
*
* 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 "kis_image.h"
#include // WORDS_BIGENDIAN
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include
#include "KisProofingConfiguration.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
#include "kis_colorspace_convert_visitor.h"
#include "kis_count_visitor.h"
#include "kis_filter_strategy.h"
#include "kis_group_layer.h"
#include "commands/kis_image_commands.h"
#include "kis_layer.h"
#include "kis_meta_data_merge_strategy_registry.h"
#include "kis_name_server.h"
#include "kis_paint_layer.h"
#include "kis_projection_leaf.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
#include "kis_meta_data_merge_strategy.h"
#include "kis_memory_statistics_server.h"
#include "kis_image_config.h"
#include "kis_update_scheduler.h"
#include "kis_image_signal_router.h"
#include "kis_image_animation_interface.h"
#include "kis_stroke_strategy.h"
#include "kis_simple_stroke_strategy.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_transform_worker.h"
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
#include "processing/kis_crop_selections_processing_visitor.h"
#include "processing/kis_transform_processing_visitor.h"
#include "commands_new/kis_image_resize_command.h"
#include "commands_new/kis_image_set_resolution_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_composite_progress_proxy.h"
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_crop_saved_extra_data.h"
#include "kis_layer_utils.h"
#include "kis_lod_transform.h"
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include "kis_sync_lod_cache_stroke_strategy.h"
#include "kis_projection_updates_filter.h"
#include "kis_layer_projection_plane.h"
#include "kis_update_time_monitor.h"
#include "tiles3/kis_lockless_stack.h"
#include
#include
#include "kis_time_range.h"
#include "KisRunnableBasedStrokeStrategy.h"
#include "KisRunnableStrokeJobData.h"
#include "KisRunnableStrokeJobUtils.h"
#include "KisRunnableStrokeJobsInterface.h"
// #define SANITY_CHECKS
#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name) \
if (!locked()) warnKrita() << "Locking policy failed:" << name \
<< "has been called without the image" \
"being locked";
#else
#define SANITY_CHECK_LOCKED(name)
#endif
struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrar() {
qRegisterMetaType("KisImageSP");
}
};
static KisImageSPStaticRegistrar __registrar;
class KisImage::KisImagePrivate
{
public:
KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
const KoColorSpace *c,
KisUndoStore *undo,
KisImageAnimationInterface *_animationInterface)
: q(_q)
, lockedForReadOnly(false)
, width(w)
, height(h)
, colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
, nserver(1)
, undoStore(undo ? undo : new KisDumbUndoStore())
, legacyUndoAdapter(undoStore.data(), _q)
, postExecutionUndoAdapter(undoStore.data(), _q)
, signalRouter(_q)
, animationInterface(_animationInterface)
, scheduler(_q, _q)
, axesCenter(QPointF(0.5, 0.5))
{
{
KisImageConfig cfg(true);
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
~KisImagePrivate() {
/**
* Stop animation interface. It may use the rootLayer.
*/
delete animationInterface;
/**
* First delete the nodes, while strokes
* and undo are still alive
*/
rootLayer.clear();
}
KisImage *q;
quint32 lockCount = 0;
bool lockedForReadOnly;
qint32 width;
qint32 height;
double xres = 1.0;
double yres = 1.0;
const KoColorSpace * colorSpace;
KisProofingConfigurationSP proofingConfig;
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
KisSelectionMaskSP targetOverlaySelectionMask; // the overlay switching stroke will try to switch into this mask
KisSelectionMaskSP overlaySelectionMask;
QList compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
KisNameServer nserver;
QScopedPointer undoStore;
KisLegacyUndoAdapter legacyUndoAdapter;
KisPostExecutionUndoAdapter postExecutionUndoAdapter;
vKisAnnotationSP annotations;
QAtomicInt disableUIUpdateSignals;
KisLocklessStack savedDisabledUIUpdates;
KisProjectionUpdatesFilterSP projectionUpdatesFilter;
KisImageSignalRouter signalRouter;
KisImageAnimationInterface *animationInterface;
KisUpdateScheduler scheduler;
QAtomicInt disableDirtyRequests;
KisCompositeProgressProxy compositeProgressProxy;
bool blockLevelOfDetail = false;
QPointF axesCenter;
bool allowMasksOnRootNode = false;
bool tryCancelCurrentStrokeAsync();
void notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs);
};
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, KisShared()
, m_d(new KisImagePrivate(this, width, height,
colorSpace, undoStore,
new KisImageAnimationInterface(this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
setObjectName(name);
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
}
KisImage::~KisImage()
{
dbgImage << "deleting kisimage" << objectName();
/**
* Request the tools to end currently running strokes
*/
waitForDone();
delete m_d;
disconnect(); // in case Qt gets confused
}
KisImageSP KisImage::fromQImage(const QImage &image, KisUndoStore *undoStore)
{
const KoColorSpace *colorSpace = 0;
switch (image.format()) {
case QImage::Format_Invalid:
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
colorSpace = KoColorSpaceRegistry::instance()->graya8();
break;
case QImage::Format_Indexed8:
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_RGB16:
colorSpace = KoColorSpaceRegistry::instance()->rgb16();
break;
case QImage::Format_ARGB8565_Premultiplied:
case QImage::Format_RGB666:
case QImage::Format_ARGB6666_Premultiplied:
case QImage::Format_RGB555:
case QImage::Format_ARGB8555_Premultiplied:
case QImage::Format_RGB888:
case QImage::Format_RGB444:
case QImage::Format_ARGB4444_Premultiplied:
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->rgb8();
break;
case QImage::Format_Alpha8:
colorSpace = KoColorSpaceRegistry::instance()->alpha8();
break;
case QImage::Format_Grayscale8:
colorSpace = KoColorSpaceRegistry::instance()->graya8();
break;
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
case QImage::Format_Grayscale16:
colorSpace = KoColorSpaceRegistry::instance()->graya16();
break;
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
colorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), 0);
break;
#endif
default:
colorSpace = 0;
}
KisImageSP img = new KisImage(undoStore, image.width(), image.height(), colorSpace, i18n("Imported Image"));
KisPaintLayerSP layer = new KisPaintLayer(img, img->nextLayerName(), 255);
layer->paintDevice()->convertFromQImage(image, 0, 0, 0);
img->addNode(layer.data(), img->rootLayer().data());
return img;
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
void KisImage::copyFromImage(const KisImage &rhs)
{
copyFromImageImpl(rhs, REPLACE);
}
void KisImage::copyFromImageImpl(const KisImage &rhs, int policy)
{
// make sure we choose exactly one from REPLACE and CONSTRUCT
KIS_ASSERT_RECOVER_RETURN((policy & REPLACE) != (policy & CONSTRUCT));
// only when replacing do we need to emit signals
#define EMIT_IF_NEEDED if (!(policy & REPLACE)) {} else emit
if (policy & REPLACE) { // if we are constructing the image, these are already set
if (m_d->width != rhs.width() || m_d->height != rhs.height()) {
m_d->width = rhs.width();
m_d->height = rhs.height();
emit sigSizeChanged(QPointF(), QPointF());
}
if (m_d->colorSpace != rhs.colorSpace()) {
m_d->colorSpace = rhs.colorSpace();
emit sigColorSpaceChanged(m_d->colorSpace);
}
}
// from KisImage::KisImage(const KisImage &, KisUndoStore *, bool)
setObjectName(rhs.objectName());
if (m_d->xres != rhs.m_d->xres || m_d->yres != rhs.m_d->yres) {
m_d->xres = rhs.m_d->xres;
m_d->yres = rhs.m_d->yres;
EMIT_IF_NEEDED sigResolutionChanged(m_d->xres, m_d->yres);
}
m_d->allowMasksOnRootNode = rhs.m_d->allowMasksOnRootNode;
if (rhs.m_d->proofingConfig) {
KisProofingConfigurationSP proofingConfig(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
if (policy & REPLACE) {
setProofingConfiguration(proofingConfig);
} else {
m_d->proofingConfig = proofingConfig;
}
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast(newRoot.data());
setRoot(newRoot);
bool exactCopy = policy & EXACT_COPY;
if (exactCopy || rhs.m_d->isolatedRootNode) {
QQueue linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(newRoot,
[&linearizedNodes, exactCopy, &rhs, this](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
if (exactCopy) {
node->setUuid(refNode->uuid());
}
if (rhs.m_d->isolatedRootNode &&
rhs.m_d->isolatedRootNode == refNode) {
m_d->isolatedRootNode = node;
}
});
}
KisLayerUtils::recursiveApplyNodes(newRoot,
[](KisNodeSP node) {
dbgImage << "Node: " << (void *)node.data();
});
m_d->compositions.clear();
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
EMIT_IF_NEEDED sigLayersChangedAsync();
m_d->nserver = rhs.m_d->nserver;
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
/**
* The overlay device is not inherited when cloning the image!
*/
if (rhs.m_d->overlaySelectionMask) {
const QRect dirtyRect = rhs.m_d->overlaySelectionMask->extent();
m_d->rootLayer->setDirty(dirtyRect);
}
#undef EMIT_IF_NEEDED
}
KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
: KisNodeFacade(),
KisNodeGraphListener(),
KisShared(),
m_d(new KisImagePrivate(this,
rhs.width(), rhs.height(),
rhs.colorSpace(),
undoStore ? undoStore : new KisDumbUndoStore(),
new KisImageAnimationInterface(*rhs.animationInterface(), this)))
{
// make sure KisImage belongs to the GUI thread
moveToThread(qApp->thread());
connect(this, SIGNAL(sigInternalStopIsolatedModeRequested()), SLOT(stopIsolatedMode()));
copyFromImageImpl(rhs, CONSTRUCT | (exactCopy ? EXACT_COPY : 0));
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
SANITY_CHECK_LOCKED("aboutToAddANode");
}
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
{
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
SANITY_CHECK_LOCKED("nodeHasBeenAdded");
m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
}
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
{
KisNodeSP deletedNode = parent->at(index);
if (!dynamic_cast(deletedNode.data()) &&
deletedNode == m_d->isolatedRootNode) {
emit sigInternalStopIsolatedModeRequested();
}
KisNodeGraphListener::aboutToRemoveANode(parent, index);
SANITY_CHECK_LOCKED("aboutToRemoveANode");
m_d->signalRouter.emitAboutToRemoveANode(parent, index);
}
void KisImage::nodeChanged(KisNode* node)
{
KisNodeGraphListener::nodeChanged(node);
requestStrokeEnd();
m_d->signalRouter.emitNodeChanged(node);
}
void KisImage::invalidateAllFrames()
{
invalidateFrames(KisTimeRange::infinite(0), QRect());
}
void KisImage::setOverlaySelectionMask(KisSelectionMaskSP mask)
{
if (m_d->targetOverlaySelectionMask == mask) return;
m_d->targetOverlaySelectionMask = mask;
struct UpdateOverlaySelectionStroke : public KisSimpleStrokeStrategy {
UpdateOverlaySelectionStroke(KisImageSP image)
: KisSimpleStrokeStrategy("update-overlay-selection-mask", kundo2_noi18n("update-overlay-selection-mask")),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
KisSelectionMaskSP oldMask = m_image->m_d->overlaySelectionMask;
KisSelectionMaskSP newMask = m_image->m_d->targetOverlaySelectionMask;
if (oldMask == newMask) return;
KIS_SAFE_ASSERT_RECOVER_RETURN(!newMask || newMask->graphListener() == m_image);
m_image->m_d->overlaySelectionMask = newMask;
if (oldMask || newMask) {
m_image->m_d->rootLayer->notifyChildMaskChanged();
}
if (oldMask) {
m_image->m_d->rootLayer->setDirtyDontResetAnimationCache(oldMask->extent());
}
if (newMask) {
newMask->setDirty();
}
m_image->undoAdapter()->emitSelectionChanged();
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new UpdateOverlaySelectionStroke(this));
endStroke(id);
}
KisSelectionMaskSP KisImage::overlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
bool KisImage::hasOverlaySelectionMask() const
{
return m_d->overlaySelectionMask;
}
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
} else {
return 0;
}
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!globalSelection) {
if (selectionMask) {
removeNode(selectionMask);
}
}
else {
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->initSelection(m_d->rootLayer);
addNode(selectionMask);
// If we do not set the selection now, the setActive call coming next
// can be very, very expensive, depending on the size of the image.
selectionMask->setSelection(globalSelection);
selectionMask->setActive(true);
}
else {
selectionMask->setSelection(globalSelection);
}
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->childCount() > 0);
KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->rootLayer->selectionMask());
}
m_d->deselectedGlobalSelection = 0;
m_d->legacyUndoAdapter.emitSelectionChanged();
}
void KisImage::deselectGlobalSelection()
{
KisSelectionSP savedSelection = globalSelection();
setGlobalSelection(0);
m_d->deselectedGlobalSelection = savedSelection;
}
bool KisImage::canReselectGlobalSelection()
{
return m_d->deselectedGlobalSelection;
}
void KisImage::reselectGlobalSelection()
{
if(m_d->deselectedGlobalSelection) {
setGlobalSelection(m_d->deselectedGlobalSelection);
}
}
QString KisImage::nextLayerName(const QString &_baseName) const
{
QString baseName = _baseName;
if (m_d->nserver.currentSeed() == 0) {
m_d->nserver.number();
return i18n("background");
}
if (baseName.isEmpty()) {
baseName = i18n("Layer");
}
return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
}
void KisImage::rollBackLayerName()
{
m_d->nserver.rollback();
}
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
return &m_d->compositeProgressProxy;
}
bool KisImage::locked() const
{
return m_d->lockCount != 0;
}
void KisImage::barrierLock(bool readOnly)
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.barrierLock();
m_d->lockedForReadOnly = readOnly;
} else {
m_d->lockedForReadOnly &= readOnly;
}
m_d->lockCount++;
}
bool KisImage::tryBarrierLock(bool readOnly)
{
bool result = true;
if (!locked()) {
result = m_d->scheduler.tryBarrierLock();
m_d->lockedForReadOnly = readOnly;
}
if (result) {
m_d->lockCount++;
m_d->lockedForReadOnly &= readOnly;
}
return result;
}
bool KisImage::isIdle(bool allowLocked)
{
return (allowLocked || !locked()) && m_d->scheduler.isIdle();
}
void KisImage::lock()
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.lock();
}
m_d->lockCount++;
m_d->lockedForReadOnly = false;
}
void KisImage::unlock()
{
Q_ASSERT(locked());
if (locked()) {
m_d->lockCount--;
if (m_d->lockCount == 0) {
m_d->scheduler.unlock(!m_d->lockedForReadOnly);
}
}
}
void KisImage::blockUpdates()
{
m_d->scheduler.blockUpdates();
}
void KisImage::unblockUpdates()
{
m_d->scheduler.unblockUpdates();
}
void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
{
if (newRect == bounds() && !cropLayers) return;
KUndo2MagicString actionName = cropLayers ?
kundo2_i18n("Crop Image") :
kundo2_i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(cropLayers ?
KisCropSavedExtraData::CROP_IMAGE :
KisCropSavedExtraData::RESIZE_IMAGE,
newRect);
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName, extraData);
if (cropLayers || !newRect.topLeft().isNull()) {
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, cropLayers, true);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
applicator.end();
}
void KisImage::resizeImage(const QRect& newRect)
{
resizeImageImpl(newRect, false);
}
void KisImage::cropImage(const QRect& newRect)
{
resizeImageImpl(newRect, true);
}
void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
bool isLayer = qobject_cast(node.data());
KUndo2MagicString actionName = isLayer ?
kundo2_i18n("Crop Layer") :
kundo2_i18n("Crop Mask");
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
newRect, node);
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName, extraData);
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, true, false);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
bool sizeChanged = size != this->size();
if (!resolutionChanged && !sizeChanged) return;
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
KUndo2MagicString actionName = sizeChanged ?
kundo2_i18n("Scale Image") :
kundo2_i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
qreal sx = qreal(size.width()) / this->size().width();
qreal sy = qreal(size.height()) / this->size().height();
QTransform shapesCorrection;
if (resolutionChanged) {
shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
}
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(sx, sy,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy,
shapesCorrection);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resolutionChanged) {
KUndo2Command *parent =
new KisResetShapesCommand(m_d->rootLayer);
new KisImageSetResolutionCommand(this, xres, yres, parent);
applicator.applyCommand(parent);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, size));
}
applicator.end();
}
void KisImage::scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection)
{
KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
QPointF offset;
{
KisTransformWorker worker(0,
scaleX, scaleY,
0, 0, 0, 0,
0.0,
0, 0, 0, 0);
QTransform transform = worker.transform();
offset = center - transform.map(center);
}
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(scaleX, scaleY,
0, 0,
QPointF(),
0,
offset.x(), offset.y(),
filterStrategy);
visitor->setSelection(selection);
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.end();
}
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
double radians,
bool resizeImage,
KisSelectionSP selection)
{
// we can either transform (and resize) the whole image or
// transform a selection, we cannot do both at the same time
KIS_SAFE_ASSERT_RECOVER(!(bool(selection) && resizeImage)) {
selection = 0;
}
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
0, 0, 0, 0,
radians,
0, 0, 0, 0);
QTransform transform = worker.transform();
if (resizeImage) {
QRect newRect = transform.mapRect(baseBounds);
newSize = newRect.size();
offset = -newRect.topLeft();
}
else {
QPointF origin = QRectF(baseBounds).center();
newSize = size();
offset = -(transform.map(origin) - origin);
}
}
bool sizeChanged = resizeImage &&
(newSize.width() != baseBounds.width() ||
newSize.height() != baseBounds.height());
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
KisProcessingApplicator::ProcessingFlags signalFlags =
sizeChanged ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, rootNode,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
QPointF(),
radians,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::rotateImage(double radians)
{
rotateImpl(kundo2_i18n("Rotate Image"), root(), radians, true, 0);
}
void KisImage::rotateNode(KisNodeSP node, double radians, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
rotateImpl(kundo2_i18n("Rotate Mask"), node, radians, false, selection);
} else {
rotateImpl(kundo2_i18n("Rotate Layer"), node, radians, false, selection);
}
}
void KisImage::shearImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double angleX, double angleY,
KisSelectionSP selection)
{
const QRect baseBounds =
resizeImage ? bounds() :
selection ? selection->selectedExactRect() :
rootNode->exactBounds();
const QPointF origin = QRectF(baseBounds).center();
//angleX, angleY are in degrees
const qreal pi = 3.1415926535897932385;
const qreal deg2rad = pi / 180.0;
qreal tanX = tan(angleX * deg2rad);
qreal tanY = tan(angleY * deg2rad);
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
tanX, tanY, origin.x(), origin.y(),
0,
0, 0, 0, 0);
QRect newRect = worker.transform().mapRect(baseBounds);
newSize = newRect.size();
if (resizeImage) offset = -newRect.topLeft();
}
if (newSize == baseBounds.size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(baseBounds, newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
KisProcessingApplicator::RECURSIVE;
if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
KisProcessingApplicator applicator(this, rootNode,
signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
KisTransformProcessingVisitor *visitor =
new KisTransformProcessingVisitor(1.0, 1.0,
tanX, tanY, origin,
0,
offset.x(), offset.y(),
filter);
if (selection) {
visitor->setSelection(selection);
}
if (selection) {
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
} else {
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
if (resizeImage) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection)
{
if (node->inherits("KisMask")) {
shearImpl(kundo2_i18n("Shear Mask"), node, false,
angleX, angleY, selection);
} else {
shearImpl(kundo2_i18n("Shear Layer"), node, false,
angleX, angleY, selection);
}
}
void KisImage::shear(double angleX, double angleY)
{
shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
angleX, angleY, 0);
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (!dstColorSpace) return;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
m_d->rootLayer->accept(visitor);
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
bool KisImage::assignImageProfile(const KoColorProfile *profile)
{
if (!profile) return false;
const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
const KoColorSpace *srcCs = colorSpace();
if (!dstCs) return false;
m_d->colorSpace = dstCs;
KisChangeProfileVisitor visitor(srcCs, dstCs);
bool retval = m_d->rootLayer->accept(visitor);
m_d->signalRouter.emitNotification(ProfileChangedSignal);
return retval;
}
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
if (*m_d->colorSpace == *dstColorSpace) return;
undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
m_d->colorSpace = colorSpace;
m_d->rootLayer->resetCache();
m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
}
const KoColorSpace * KisImage::colorSpace() const
{
return m_d->colorSpace;
}
const KoColorProfile * KisImage::profile() const
{
return colorSpace()->profile();
}
double KisImage::xRes() const
{
return m_d->xres;
}
double KisImage::yRes() const
{
return m_d->yres;
}
void KisImage::setResolution(double xres, double yres)
{
m_d->xres = xres;
m_d->yres = yres;
m_d->signalRouter.emitNotification(ResolutionChangedSignal);
}
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
{
return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
}
QPoint KisImage::documentToImagePixelFloored(const QPointF &documentCoord) const
{
QPointF pixelCoord = documentToPixel(documentCoord);
return QPoint(qFloor(pixelCoord.x()), qFloor(pixelCoord.y()));
}
QRectF KisImage::documentToPixel(const QRectF &documentRect) const
{
return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
}
QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
{
return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
}
QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
{
return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
}
QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
{
return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
}
qint32 KisImage::width() const
{
return m_d->width;
}
qint32 KisImage::height() const
{
return m_d->height;
}
KisGroupLayerSP KisImage::rootLayer() const
{
Q_ASSERT(m_d->rootLayer);
return m_d->rootLayer;
}
KisPaintDeviceSP KisImage::projection() const
{
if (m_d->isolatedRootNode) {
return m_d->isolatedRootNode->projection();
}
Q_ASSERT(m_d->rootLayer);
KisPaintDeviceSP projection = m_d->rootLayer->projection();
Q_ASSERT(projection);
return projection;
}
qint32 KisImage::nlayers() const
{
QStringList list;
list << "KisLayer";
KisCountVisitor visitor(list, KoProperties());
m_d->rootLayer->accept(visitor);
return visitor.count();
}
qint32 KisImage::nHiddenLayers() const
{
QStringList list;
list << "KisLayer";
KoProperties properties;
properties.setProperty("visible", false);
KisCountVisitor visitor(list, properties);
m_d->rootLayer->accept(visitor);
return visitor.count();
}
void KisImage::flatten(KisNodeSP activeNode)
{
KisLayerUtils::flattenImage(this, activeNode);
}
void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter)
{
if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
}
}
void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
KisLayerUtils::mergeDown(this, layer, strategy);
}
void KisImage::flattenLayer(KisLayerSP layer)
{
KisLayerUtils::flattenLayer(this, layer);
}
void KisImage::setModified()
{
m_d->signalRouter.emitNotification(ModifiedSignal);
}
QImage KisImage::convertToQImage(QRect imageRect,
const KoColorProfile * profile)
{
qint32 x;
qint32 y;
qint32 w;
qint32 h;
imageRect.getRect(&x, &y, &w, &h);
return convertToQImage(x, y, w, h, profile);
}
QImage KisImage::convertToQImage(qint32 x,
qint32 y,
qint32 w,
qint32 h,
const KoColorProfile * profile)
{
KisPaintDeviceSP dev = projection();
if (!dev) return QImage();
QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return image;
}
QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
{
if (scaledImageSize.isEmpty()) {
return QImage();
}
KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
KisPainter gc;
gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
gc.end();
double scaleX = qreal(scaledImageSize.width()) / width();
double scaleY = qreal(scaledImageSize.height()) / height();
QPointer updater = new KoDummyUpdater();
KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
worker.run();
delete updater;
return dev->convertToQImage(profile);
}
void KisImage::notifyLayersChanged()
{
m_d->signalRouter.emitNotification(LayersChangedSignal);
}
QRect KisImage::bounds() const
{
return QRect(0, 0, width(), height());
}
QRect KisImage::effectiveLodBounds() const
{
QRect boundRect = bounds();
const int lod = currentLevelOfDetail();
if (lod > 0) {
KisLodTransform t(lod);
boundRect = t.map(boundRect);
}
return boundRect;
}
KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
{
const int lod = currentLevelOfDetail();
return lod > 0 ?
m_d->scheduler.lodNPostExecutionUndoAdapter() :
&m_d->postExecutionUndoAdapter;
}
const KUndo2Command* KisImage::lastExecutedCommand() const
{
return m_d->undoStore->presentCommand();
}
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
m_d->legacyUndoAdapter.setUndoStore(undoStore);
m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
m_d->undoStore.reset(undoStore);
}
KisUndoStore* KisImage::undoStore()
{
return m_d->undoStore.data();
}
KisUndoAdapter* KisImage::undoAdapter() const
{
return &m_d->legacyUndoAdapter;
}
void KisImage::setDefaultProjectionColor(const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
m_d->rootLayer->setDefaultProjectionColor(color);
}
KoColor KisImage::defaultProjectionColor() const
{
KIS_ASSERT_RECOVER(m_d->rootLayer) {
return KoColor(Qt::transparent, m_d->colorSpace);
}
return m_d->rootLayer->defaultProjectionColor();
}
void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
{
emit sigInternalStopIsolatedModeRequested();
KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
if (m_d->rootLayer) {
m_d->rootLayer->setGraphListener(0);
m_d->rootLayer->disconnect();
KisPaintDeviceSP original = m_d->rootLayer->original();
defaultProjectionColor = original->defaultPixel();
}
m_d->rootLayer = rootLayer;
m_d->rootLayer->disconnect();
m_d->rootLayer->setGraphListener(this);
m_d->rootLayer->setImage(this);
setRoot(m_d->rootLayer.data());
this->setDefaultProjectionColor(defaultProjectionColor);
}
void KisImage::addAnnotation(KisAnnotationSP annotation)
{
// Find the icc annotation, if there is one
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == annotation->type()) {
*it = annotation;
return;
}
++it;
}
m_d->annotations.push_back(annotation);
}
KisAnnotationSP KisImage::annotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
return *it;
}
++it;
}
return KisAnnotationSP(0);
}
void KisImage::removeAnnotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
m_d->annotations.erase(it);
return;
}
++it;
}
}
vKisAnnotationSP_it KisImage::beginAnnotations()
{
return m_d->annotations.begin();
}
vKisAnnotationSP_it KisImage::endAnnotations()
{
return m_d->annotations.end();
}
void KisImage::notifyAboutToBeDeleted()
{
emit sigAboutToBeDeleted();
}
KisImageSignalRouter* KisImage::signalRouter()
{
return &m_d->signalRouter;
}
void KisImage::waitForDone()
{
requestStrokeEnd();
m_d->scheduler.waitForDone();
}
KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
{
/**
* Ask open strokes to end gracefully. All the strokes clients
* (including the one calling this method right now) will get
* a notification that they should probably end their strokes.
* However this is purely their choice whether to end a stroke
* or not.
*/
if (strokeStrategy->requestsOtherStrokesToEnd()) {
requestStrokeEnd();
}
/**
* Some of the strokes can cancel their work with undoing all the
* changes they did to the paint devices. The problem is that undo
* stack will know nothing about it. Therefore, just notify it
* explicitly
*/
if (strokeStrategy->clearsRedoOnStart()) {
m_d->undoStore->purgeRedoState();
}
return m_d->scheduler.startStroke(strokeStrategy);
}
void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc, QVector &jobs)
{
KisImageConfig imageConfig(true);
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < rc.height(); y += patchHeight) {
for (int x = 0; x < rc.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
patchRect &= rc;
KritaUtils::addJobConcurrent(jobs, std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
}
}
}
bool KisImage::startIsolatedMode(KisNodeSP node)
{
struct StartIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
StartIsolatedModeStroke(KisNodeSP node, KisImageSP image)
: KisRunnableBasedStrokeStrategy("start-isolated-mode", kundo2_noi18n("start-isolated-mode")),
m_node(node),
m_image(image)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
this->enableJob(JOB_DOSTROKE, true);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
// pass-though node don't have any projection prepared, so we should
// explicitly regenerate it before activating isolated mode.
m_node->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
m_image->m_d->isolatedRootNode = m_node;
emit m_image->sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
QVector jobs;
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
this->runnableJobsInterface()->addRunnableJobs(jobs);
m_image->invalidateAllFrames();
}
private:
KisNodeSP m_node;
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StartIsolatedModeStroke(node, this));
endStroke(id);
return true;
}
void KisImage::stopIsolatedMode()
{
if (!m_d->isolatedRootNode) return;
struct StopIsolatedModeStroke : public KisRunnableBasedStrokeStrategy {
StopIsolatedModeStroke(KisImageSP image)
: KisRunnableBasedStrokeStrategy("stop-isolated-mode", kundo2_noi18n("stop-isolated-mode")),
m_image(image)
{
this->enableJob(JOB_INIT);
this->enableJob(JOB_DOSTROKE, true);
setClearsRedoOnStart(false);
}
void initStrokeCallback() {
if (!m_image->m_d->isolatedRootNode) return;
//KisNodeSP oldRootNode = m_image->m_d->isolatedRootNode;
m_image->m_d->isolatedRootNode = 0;
emit m_image->sigIsolatedModeChanged();
m_image->invalidateAllFrames();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
QVector jobs;
m_image->m_d->notifyProjectionUpdatedInPatches(m_image->bounds(), jobs);
this->runnableJobsInterface()->addRunnableJobs(jobs);
// TODO: Substitute notifyProjectionUpdated() with this code
// when update optimization is implemented
//
// QRect updateRect = bounds() | oldRootNode->extent();
// oldRootNode->setDirty(updateRect);
}
private:
KisImageSP m_image;
};
KisStrokeId id = startStroke(new StopIsolatedModeStroke(this));
endStroke(id);
}
KisNodeSP KisImage::isolatedModeRoot() const
{
return m_d->isolatedRootNode;
}
void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
{
KisUpdateTimeMonitor::instance()->reportJobStarted(data);
m_d->scheduler.addJob(id, data);
}
void KisImage::endStroke(KisStrokeId id)
{
m_d->scheduler.endStroke(id);
}
bool KisImage::cancelStroke(KisStrokeId id)
{
return m_d->scheduler.cancelStroke(id);
}
bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
{
return scheduler.tryCancelCurrentStrokeAsync();
}
void KisImage::requestUndoDuringStroke()
{
emit sigUndoDuringStrokeRequested();
}
void KisImage::requestStrokeCancellation()
{
if (!m_d->tryCancelCurrentStrokeAsync()) {
emit sigStrokeCancellationRequested();
}
}
UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
{
return m_d->scheduler.tryUndoLastStrokeAsync();
}
void KisImage::requestStrokeEnd()
{
emit sigStrokeEndRequested();
emit sigStrokeEndRequestedActiveNodeFiltered();
}
void KisImage::requestStrokeEndActiveNode()
{
emit sigStrokeEndRequested();
}
void KisImage::refreshGraph(KisNodeSP root)
{
refreshGraph(root, bounds(), bounds());
}
void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefresh(root, rc, cropRect);
}
void KisImage::initialRefreshGraph()
{
/**
* NOTE: Tricky part. We set crop rect to null, so the clones
* will not rely on precalculated projections of their sources
*/
refreshGraphAsync(0, bounds(), QRect());
waitForDone();
}
void KisImage::refreshGraphAsync(KisNodeSP root)
{
refreshGraphAsync(root, bounds(), bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
{
refreshGraphAsync(root, rc, bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefreshAsync(root, rc, cropRect);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
}
void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->scheduler.addSpontaneousJob(spontaneousJob);
}
+bool KisImage::hasUpdatesRunning() const
+{
+ return m_d->scheduler.hasUpdatesRunning();
+}
+
void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
// update filters are *not* recursive!
KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter);
m_d->projectionUpdatesFilter = filter;
}
KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const
{
return m_d->projectionUpdatesFilter;
}
void KisImage::disableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter()));
}
void KisImage::enableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
}
void KisImage::disableUIUpdates()
{
m_d->disableUIUpdateSignals.ref();
}
void KisImage::notifyBatchUpdateStarted()
{
m_d->signalRouter.emitNotifyBatchUpdateStarted();
}
void KisImage::notifyBatchUpdateEnded()
{
m_d->signalRouter.emitNotifyBatchUpdateEnded();
}
void KisImage::notifyUIUpdateCompleted(const QRect &rc)
{
notifyProjectionUpdated(rc);
}
QVector KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
QRect rect;
QVector postponedUpdates;
while (m_d->savedDisabledUIUpdates.pop(rect)) {
postponedUpdates.append(rect);
}
return postponedUpdates;
}
void KisImage::notifyProjectionUpdated(const QRect &rc)
{
KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
if (!m_d->disableUIUpdateSignals) {
int lod = currentLevelOfDetail();
QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
if (dirtyRect.isEmpty()) return;
emit sigImageUpdated(dirtyRect);
} else {
m_d->savedDisabledUIUpdates.push(rc);
}
}
void KisImage::setWorkingThreadsLimit(int value)
{
m_d->scheduler.setThreadsLimit(value);
}
int KisImage::workingThreadsLimit() const
{
return m_d->scheduler.threadsLimit();
}
void KisImage::notifySelectionChanged()
{
/**
* The selection is calculated asynchromously, so it is not
* handled by disableUIUpdates() and other special signals of
* KisImageSignalRouter
*/
m_d->legacyUndoAdapter.emitSelectionChanged();
/**
* Editing of selection masks doesn't necessary produce a
* setDirty() call, so in the end of the stroke we need to request
* direct update of the UI's cache.
*/
if (m_d->isolatedRootNode &&
dynamic_cast(m_d->isolatedRootNode.data())) {
notifyProjectionUpdated(bounds());
}
}
void KisImage::requestProjectionUpdateImpl(KisNode *node,
const QVector &rects,
const QRect &cropRect)
{
if (rects.isEmpty()) return;
m_d->scheduler.updateProjection(node, rects, cropRect);
}
void KisImage::requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache)
{
if (m_d->projectionUpdatesFilter
&& m_d->projectionUpdatesFilter->filter(this, node, rects, resetAnimationCache)) {
return;
}
if (resetAnimationCache) {
m_d->animationInterface->notifyNodeChanged(node, rects, false);
}
/**
* Here we use 'permitted' instead of 'active' intentively,
* because the updates may come after the actual stroke has been
* finished. And having some more updates for the stroke not
* supporting the wrap-around mode will not make much harm.
*/
if (m_d->wrapAroundModePermitted) {
QVector allSplitRects;
const QRect boundRect = effectiveLodBounds();
Q_FOREACH (const QRect &rc, rects) {
KisWrappedRect splitRect(rc, boundRect);
allSplitRects.append(splitRect);
}
requestProjectionUpdateImpl(node, allSplitRects, boundRect);
} else {
requestProjectionUpdateImpl(node, rects, bounds());
}
KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
}
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
KisNode *KisImage::graphOverlayNode() const
{
return m_d->overlaySelectionMask.data();
}
QList KisImage::compositions()
{
return m_d->compositions;
}
void KisImage::addComposition(KisLayerCompositionSP composition)
{
m_d->compositions.append(composition);
}
void KisImage::removeComposition(KisLayerCompositionSP composition)
{
m_d->compositions.removeAll(composition);
}
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
{
KisSelectionMask *mask = dynamic_cast(root.data());
if (mask &&
(!bounds.contains(mask->paintDevice()->exactBounds()) ||
mask->selection()->hasShapeSelection())) {
return true;
}
KisNodeSP node = root->firstChild();
while (node) {
if (checkMasksNeedConversion(node, bounds)) {
return true;
}
node = node->nextSibling();
}
return false;
}
void KisImage::setWrapAroundModePermitted(bool value)
{
if (m_d->wrapAroundModePermitted != value) {
requestStrokeEnd();
}
m_d->wrapAroundModePermitted = value;
if (m_d->wrapAroundModePermitted &&
checkMasksNeedConversion(root(), bounds())) {
KisProcessingApplicator applicator(this, root(),
KisProcessingApplicator::RECURSIVE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Crop Selections"));
KisProcessingVisitorSP visitor =
new KisCropSelectionsProcessingVisitor(bounds());
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
}
bool KisImage::wrapAroundModePermitted() const
{
return m_d->wrapAroundModePermitted;
}
bool KisImage::wrapAroundModeActive() const
{
return m_d->wrapAroundModePermitted &&
m_d->scheduler.wrapAroundModeSupported();
}
void KisImage::setDesiredLevelOfDetail(int lod)
{
if (m_d->blockLevelOfDetail) {
qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
<< "was called while LoD functionality was being blocked!";
return;
}
m_d->scheduler.setDesiredLevelOfDetail(lod);
}
int KisImage::currentLevelOfDetail() const
{
if (m_d->blockLevelOfDetail) {
return 0;
}
return m_d->scheduler.currentLevelOfDetail();
}
void KisImage::setLevelOfDetailBlocked(bool value)
{
KisImageBarrierLockerRaw l(this);
if (value && !m_d->blockLevelOfDetail) {
m_d->scheduler.setDesiredLevelOfDetail(0);
}
m_d->blockLevelOfDetail = value;
}
void KisImage::explicitRegenerateLevelOfDetail()
{
if (!m_d->blockLevelOfDetail) {
m_d->scheduler.explicitRegenerateLevelOfDetail();
}
}
bool KisImage::levelOfDetailBlocked() const
{
return m_d->blockLevelOfDetail;
}
void KisImage::notifyNodeCollpasedChanged()
{
emit sigNodeCollapsedChanged();
}
KisImageAnimationInterface* KisImage::animationInterface() const
{
return m_d->animationInterface;
}
void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
{
m_d->proofingConfig = proofingConfig;
emit sigProofingConfigChanged();
}
KisProofingConfigurationSP KisImage::proofingConfiguration() const
{
if (m_d->proofingConfig) {
return m_d->proofingConfig;
}
return KisProofingConfigurationSP();
}
QPointF KisImage::mirrorAxesCenter() const
{
return m_d->axesCenter;
}
void KisImage::setMirrorAxesCenter(const QPointF &value) const
{
m_d->axesCenter = value;
}
void KisImage::setAllowMasksOnRootNode(bool value)
{
m_d->allowMasksOnRootNode = value;
}
bool KisImage::allowMasksOnRootNode() const
{
return m_d->allowMasksOnRootNode;
}
diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h
index dea01e86a1..c259317d69 100644
--- a/libs/image/kis_image.h
+++ b/libs/image/kis_image.h
@@ -1,1185 +1,1193 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2007 Boudewijn Rempt
*
* 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.
*/
#ifndef KIS_IMAGE_H_
#define KIS_IMAGE_H_
#include
#include
#include
#include
#include
#include
#include
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_node_graph_listener.h"
#include "kis_node_facade.h"
#include "kis_image_interfaces.h"
#include "kis_strokes_queue_undo_result.h"
#include
class KoColorSpace;
class KoColor;
class KisCompositeProgressProxy;
class KisUndoStore;
class KisUndoAdapter;
class KisImageSignalRouter;
class KisPostExecutionUndoAdapter;
class KisFilterStrategy;
class KoColorProfile;
class KisLayerComposition;
class KisSpontaneousJob;
class KisImageAnimationInterface;
class KUndo2MagicString;
class KisProofingConfiguration;
class KisPaintDevice;
namespace KisMetaData
{
class MergeStrategy;
}
/**
* This is the image class, it contains a tree of KisLayer stack and
* meta information about the image. And it also provides some
* functions to manipulate the whole image.
*/
class KRITAIMAGE_EXPORT KisImage : public QObject,
public KisStrokesFacade,
public KisStrokeUndoFacade,
public KisUpdatesFacade,
public KisProjectionUpdateListener,
public KisNodeFacade,
public KisNodeGraphListener,
public KisShared
{
Q_OBJECT
public:
/// @p colorSpace can be null. In that case, it will be initialised to a default color space.
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name);
~KisImage() override;
static KisImageSP fromQImage(const QImage &image, KisUndoStore *undoStore);
public: // KisNodeGraphListener implementation
void aboutToAddANode(KisNode *parent, int index) override;
void nodeHasBeenAdded(KisNode *parent, int index) override;
void aboutToRemoveANode(KisNode *parent, int index) override;
void nodeChanged(KisNode * node) override;
void invalidateAllFrames() override;
void notifySelectionChanged() override;
void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override;
void invalidateFrames(const KisTimeRange &range, const QRect &rect) override;
void requestTimeSwitch(int time) override;
KisNode* graphOverlayNode() const override;
public: // KisProjectionUpdateListener implementation
void notifyProjectionUpdated(const QRect &rc) override;
public:
/**
* Set the number of threads used by the image's working threads
*/
void setWorkingThreadsLimit(int value);
/**
* Return the number of threads available to the image's working threads
*/
int workingThreadsLimit() const;
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage *clone(bool exactCopy = false);
void copyFromImage(const KisImage &rhs);
private:
// must specify exactly one from CONSTRUCT or REPLACE.
enum CopyPolicy {
CONSTRUCT = 1, ///< we are copy-constructing a new KisImage
REPLACE = 2, ///< we are replacing the current KisImage with another
EXACT_COPY = 4, /// we need an exact copy of the original image
};
void copyFromImageImpl(const KisImage &rhs, int policy);
public:
/**
* Render the projection onto a QImage.
*/
QImage convertToQImage(qint32 x1,
qint32 y1,
qint32 width,
qint32 height,
const KoColorProfile * profile);
/**
* Render the projection onto a QImage.
* (this is an overloaded function)
*/
QImage convertToQImage(QRect imageRect,
const KoColorProfile * profile);
/**
* Render a thumbnail of the projection onto a QImage.
*/
QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile);
/**
* [low-level] Lock the image without waiting for all the internal job queues are processed
*
* WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead!
*
* Waits for all the **currently running** internal jobs to complete and locks the image
* for writing. Please note that this function does **not** wait for all the internal
* queues to process, so there might be some non-finished actions pending. It means that
* you just postpone these actions until you unlock() the image back. Until then, then image
* might easily be frozen in some inconsistent state.
*
* The only sane usage for this function is to lock the image for **emergency**
* processing, when some internal action or scheduler got hung up, and you just want
* to fetch some data from the image without races.
*
* In all other cases, please use barrierLock() instead!
*/
void lock();
/**
* Unlocks the image and starts/resumes all the pending internal jobs. If the image
* has been locked for a non-readOnly access, then all the internal caches of the image
* (e.g. lod-planes) are reset and regeneration jobs are scheduled.
*/
void unlock();
/**
* @return return true if the image is in a locked state, i.e. all the internal
* jobs are blocked from execution by calling wither lock() or barrierLock().
*
* When the image is locked, the user can do some modifications to the image
* contents safely without a perspective having race conditions with internal
* image jobs.
*/
bool locked() const;
/**
* Sets the mask (it must be a part of the node hierarchy already) to be paited on
* the top of all layers. This method does all the locking and syncing for you. It
* is executed asynchronously.
*/
void setOverlaySelectionMask(KisSelectionMaskSP mask);
/**
* \see setOverlaySelectionMask
*/
KisSelectionMaskSP overlaySelectionMask() const;
/**
* \see setOverlaySelectionMask
*/
bool hasOverlaySelectionMask() const;
/**
* @return the global selection object or 0 if there is none. The
* global selection is always read-write.
*/
KisSelectionSP globalSelection() const;
/**
* Retrieve the next automatic layername (XXX: fix to add option to return Mask X)
*/
QString nextLayerName(const QString &baseName = "") const;
/**
* Set the automatic layer name counter one back.
*/
void rollBackLayerName();
/**
* @brief start asynchronous operation on resizing the image
*
* The method will resize the image to fit the new size without
* dropping any pixel data. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be visible
* after operation is completed
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void resizeImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping the image
*
* The method will **drop** all the image data outside \p newRect
* and resize the image to fit the new size. The GUI will get correct
* notification with old and new sizes, so it adjust canvas origin
* accordingly and avoid jumping of the canvas on screen
*
* @param newRect the rectangle of the image which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropImage(const QRect& newRect);
/**
* @brief start asynchronous operation on cropping a subtree of nodes starting at \p node
*
* The method will **drop** all the layer data outside \p newRect. Neither
* image nor a layer will be moved anywhere
*
* @param node node to crop
* @param newRect the rectangle of the layer which will be cut-out
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void cropNode(KisNodeSP node, const QRect& newRect);
/**
* @brief start asynchronous operation on scaling the image
* @param size new image size in pixels
* @param xres new image x-resolution pixels-per-pt
* @param yres new image y-resolution pixels-per-pt
* @param filterStrategy filtering strategy
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy);
/**
* @brief start asynchronous operation on scaling a subtree of nodes starting at \p node
* @param node node to scale
* @param center the center of the scaling
* @param scaleX x-scale coefficient to be applied to the node
* @param scaleY y-scale coefficient to be applied to the node
* @param filterStrategy filtering strategy
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the image having new size
* right after this call.
*/
void scaleNode(KisNodeSP node, const QPointF ¢er, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy, KisSelectionSP selection);
/**
* @brief start asynchronous operation on rotating the image
*
* The image is resized to fit the rotated rectangle
*
* @param radians rotation angle in radians
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateImage(double radians);
/**
* @brief start asynchronous operation on rotating a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param radians rotation angle in radians
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void rotateNode(KisNodeSP node, double radians, KisSelectionSP selection);
/**
* @brief start asynchronous operation on shearing the image
*
* The image is resized to fit the sheared polygon
*
* @p angleX, @p angleY are given in degrees.
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shear(double angleX, double angleY);
/**
* @brief start asynchronous operation on shearing a subtree of nodes starting at \p node
*
* The image is not resized!
*
* @param node the root of the subtree to rotate
* @param angleX x-shear given in degrees.
* @param angleY y-shear given in degrees.
* @param selection the selection we based on
*
* Please note that the actual operation starts asynchronously in
* a background, so you cannot expect the operation being completed
* right after the call
*/
void shearNode(KisNodeSP node, double angleX, double angleY, KisSelectionSP selection);
/**
* Convert the image and all its layers to the dstColorSpace
*/
void convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
/**
* Set the color space of the projection (and the root layer)
* to dstColorSpace. No conversion is done for other layers,
* their colorspace can differ.
* @note No conversion is done, only regeneration, so no rendering
* intent needed
*/
void convertProjectionColorSpace(const KoColorSpace *dstColorSpace);
// Get the profile associated with this image
const KoColorProfile * profile() const;
/**
* Set the profile of the image to the new profile and do the same for
* all layers that have the same colorspace and profile of the image.
* It doesn't do any pixel conversion.
*
* This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*
* This does not create an undo action; only call it when creating or
* loading an image.
*
* @returns false if the profile could not be assigned
*/
bool assignImageProfile(const KoColorProfile *profile);
/**
* Returns the current undo adapter. You can add new commands to the
* undo stack using the adapter. This adapter is used for a backward
* compatibility for old commands created before strokes. It blocks
* all the porcessing at the scheduler, waits until it's finished
* and executes commands exclusively.
*/
KisUndoAdapter* undoAdapter() const;
/**
* This adapter is used by the strokes system. The commands are added
* to it *after* redo() is done (in the scheduler context). They are
* wrapped into a special command and added to the undo stack. redo()
* in not called.
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const override;
/**
* Return the lastly executed LoD0 command. It is effectively the same
* as to call undoAdapter()->presentCommand();
*/
- const KUndo2Command* lastExecutedCommand() const;
+ const KUndo2Command* lastExecutedCommand() const override;
/**
* Replace current undo store with the new one. The old store
* will be deleted.
* This method is used by KisDocument for dropping all the commands
* during file loading.
*/
void setUndoStore(KisUndoStore *undoStore);
/**
* Return current undo store of the image
*/
KisUndoStore* undoStore();
/**
* Tell the image it's modified; this emits the sigImageModified
* signal. This happens when the image needs to be saved
*/
void setModified();
/**
* The default colorspace of this image: new layers will have this
* colorspace and the projection will have this colorspace.
*/
const KoColorSpace * colorSpace() const;
/**
* X resolution in pixels per pt
*/
double xRes() const;
/**
* Y resolution in pixels per pt
*/
double yRes() const;
/**
* Set the resolution in pixels per pt.
*/
void setResolution(double xres, double yres);
/**
* Convert a document coordinate to a pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPointF documentToPixel(const QPointF &documentCoord) const;
/**
* Convert a document coordinate to an integer pixel coordinate rounded down.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPoint documentToImagePixelFloored(const QPointF &documentCoord) const;
/**
* Convert a document rectangle to a pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRectF documentToPixel(const QRectF &documentRect) const;
/**
* Convert a pixel coordinate to a document coordinate.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPointF &pixelCoord) const;
/**
* Convert an integer pixel coordinate to a document coordinate.
* The document coordinate is at the centre of the pixel.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPoint &pixelCoord) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param pixelCoord pixel coordinate to convert.
*/
QRectF pixelToDocument(const QRectF &pixelCoord) const;
/**
* Return the width of the image
*/
qint32 width() const;
/**
* Return the height of the image
*/
qint32 height() const;
/**
* Return the size of the image
*/
QSize size() const {
return QSize(width(), height());
}
/**
* @return the root node of the image node graph
*/
KisGroupLayerSP rootLayer() const;
/**
* Return the projection; that is, the complete, composited
* representation of this image.
*/
KisPaintDeviceSP projection() const;
/**
* Return the number of layers (not other nodes) that are in this
* image.
*/
qint32 nlayers() const;
/**
* Return the number of layers (not other node types) that are in
* this image and that are hidden.
*/
qint32 nHiddenLayers() const;
/**
* Merge all visible layers and discard hidden ones.
*/
void flatten(KisNodeSP activeNode);
/**
* Merge the specified layer with the layer
* below this layer, remove the specified layer.
*/
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy);
/**
* flatten the layer: that is, the projection becomes the layer
* and all subnodes are removed. If this is not a paint layer, it will morph
* into a paint layer.
*/
void flattenLayer(KisLayerSP layer);
/**
* Merges layers in \p mergedLayers and creates a new layer above
* \p putAfter
*/
void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter);
/// @return the exact bounds of the image in pixel coordinates.
- QRect bounds() const;
+ QRect bounds() const override;
/**
* Returns the actual bounds of the image, taking LevelOfDetail
* into account. This value is used as a bounds() value of
* KisDefaultBounds object.
*/
QRect effectiveLodBounds() const;
/// use if the layers have changed _completely_ (eg. when flattening)
void notifyLayersChanged();
/**
* Sets the default color of the root layer projection. All the layers
* will be merged on top of this very color
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* \see setDefaultProjectionColor()
*/
KoColor defaultProjectionColor() const;
void setRootLayer(KisGroupLayerSP rootLayer);
/**
* Add an annotation for this image. This can be anything: Gamma, EXIF, etc.
* Note that the "icc" annotation is reserved for the color strategies.
* If the annotation already exists, overwrite it with this one.
*/
void addAnnotation(KisAnnotationSP annotation);
/** get the annotation with the given type, can return 0 */
KisAnnotationSP annotation(const QString& type);
/** delete the annotation, if the image contains it */
void removeAnnotation(const QString& type);
/**
* Start of an iteration over the annotations of this image (including the ICC Profile)
*/
vKisAnnotationSP_it beginAnnotations();
/** end of an iteration over the annotations of this image */
vKisAnnotationSP_it endAnnotations();
/**
* Called before the image is deleted and sends the sigAboutToBeDeleted signal
*/
void notifyAboutToBeDeleted();
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
/**
* Returns the layer compositions for the image
*/
QList compositions();
/**
* Adds a new layer composition, will be saved with the image
*/
void addComposition(KisLayerCompositionSP composition);
/**
* Remove the layer compostion
*/
void removeComposition(KisLayerCompositionSP composition);
/**
* Permit or deny the wrap-around mode for all the paint devices
* of the image. Note that permitting the wraparound mode will not
* necessarily activate it right now. To be activated the wrap
* around mode should be 1) permitted; 2) supported by the
* currently running stroke.
*/
void setWrapAroundModePermitted(bool value);
/**
* \return whether the wrap-around mode is permitted for this
* image. If the wrap around mode is permitted and the
* currently running stroke supports it, the mode will be
* activated for all paint devices of the image.
*
* \see setWrapAroundMode
*/
bool wrapAroundModePermitted() const;
/**
* \return whether the wraparound mode is activated for all the
* devices of the image. The mode is activated when both
* factors are true: the user permitted it and the stroke
* supports it
*/
bool wrapAroundModeActive() const;
/**
* \return current level of detail which is used when processing the image.
* Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is
* null, which means we work on the original image.
*/
int currentLevelOfDetail() const;
/**
* Notify KisImage which level of detail should be used in the
* lod-mode. Setting the mode does not guarantee the LOD to be
* used. It will be activated only when the stokes supports it.
*/
void setDesiredLevelOfDetail(int lod);
/**
* Relative position of the mirror axis center
* 0,0 - topleft corner of the image
* 1,1 - bottomright corner of the image
*/
QPointF mirrorAxesCenter() const;
/**
* Sets the relative position of the axes center
* \see mirrorAxesCenter() for details
*/
void setMirrorAxesCenter(const QPointF &value) const;
/**
* Configure the image to allow masks on the root not (as reported by
* root()->allowAsChild()). By default it is not allowed (because it
* looks weird from GUI point of view)
*/
void setAllowMasksOnRootNode(bool value);
/**
* \see setAllowMasksOnRootNode()
*/
bool allowMasksOnRootNode() const;
public Q_SLOTS:
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
public:
/**
* Blocks usage of level of detail functionality. After this method
* has been called, no new strokes will use LoD.
*/
void setLevelOfDetailBlocked(bool value);
/**
* \see setLevelOfDetailBlocked()
*/
bool levelOfDetailBlocked() const;
/**
* Notifies that the node collapsed state has changed
*/
void notifyNodeCollpasedChanged();
KisImageAnimationInterface *animationInterface() const;
/**
* @brief setProofingConfiguration, this sets the image's proofing configuration, and signals
* the proofingConfiguration has changed.
* @param proofingConfig - the kis proofing config that will be used instead.
*/
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig);
/**
* @brief proofingConfiguration
* @return the proofing configuration of the image.
*/
KisProofingConfigurationSP proofingConfiguration() const;
public Q_SLOTS:
bool startIsolatedMode(KisNodeSP node);
void stopIsolatedMode();
public:
KisNodeSP isolatedModeRoot() const;
Q_SIGNALS:
/**
* Emitted whenever an action has caused the image to be
* recomposited. Parameter is the rect that has been recomposited.
*/
void sigImageUpdated(const QRect &);
/**
Emitted whenever the image has been modified, so that it
doesn't match with the version saved on disk.
*/
void sigImageModified();
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the receiver the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
/**
* Inform the model that a node was changed
*/
void sigNodeChanged(KisNodeSP node);
/**
* Inform that the image is going to be deleted
*/
void sigAboutToBeDeleted();
/**
* The signal is emitted right after a node has been connected
* to the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread. If you need
* information about the parent/siblings of the node connect
* with Qt::DirectConnection, get needed information and then
* emit another Qt::AutoConnection signal to pass this information
* to your thread. See details of the implementation
* in KisDummiesfacadeBase.
*/
void sigNodeAddedAsync(KisNodeSP node);
/**
* This signal is emitted right before a node is going to removed
* from the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread.
*
* \see comment in sigNodeAddedAsync()
*/
void sigRemoveNodeAsync(KisNodeSP node);
/**
* Emitted when the root node of the image has changed.
* It happens, e.g. when we flatten the image. When
* this happens the receiver should reload information
* about the image
*/
void sigLayersChangedAsync();
/**
* Emitted when the UI has requested the undo of the last stroke's
* operation. The point is, we cannot deal with the internals of
* the stroke without its creator knowing about it (which most
* probably cause a crash), so we just forward this request from
* the UI to the creator of the stroke.
*
* If your tool supports undoing part of its work, just listen to
* this signal and undo when it comes
*/
void sigUndoDuringStrokeRequested();
/**
* Emitted when the UI has requested the cancellation of
* the stroke. The point is, we cannot cancel the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports cancelling of its work in the middle
* of operation, just listen to this signal and cancel
* the stroke when it comes
*/
void sigStrokeCancellationRequested();
/**
* Emitted when the image decides that the stroke should better
* be ended. The point is, we cannot just end the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports long strokes that may involve multiple
* mouse actions in one stroke, just listen to this signal and
* end the stroke when it comes.
*/
void sigStrokeEndRequested();
/**
* Same as sigStrokeEndRequested() but is not emitted when the active node
* is changed.
*/
void sigStrokeEndRequestedActiveNodeFiltered();
/**
* Emitted when the isolated mode status has changed.
*
* Can be used by the receivers to catch a fact of forcefully
* stopping the isolated mode by the image when some complex
* action was requested
*/
void sigIsolatedModeChanged();
/**
* Emitted when one or more nodes changed the collapsed state
*
*/
void sigNodeCollapsedChanged();
/**
* Emitted when the proofing configuration of the image is being changed.
*
*/
void sigProofingConfigChanged();
/**
* Internal signal for asynchronously requesting isolated mode to stop. Don't use it
* outside KisImage, use sigIsolatedModeChanged() instead.
*/
void sigInternalStopIsolatedModeRequested();
public Q_SLOTS:
KisCompositeProgressProxy* compositeProgressProxy();
bool isIdle(bool allowLocked = false);
/**
* @brief Wait until all the queued background jobs are completed and lock the image.
*
* KisImage object has a local scheduler that executes long-running image
* rendering/modifying jobs (we call them "strokes") in a background. Basically,
* one should either access the image from the scope of such jobs (strokes) or
* just lock the image before using.
*
* Calling barrierLock() will wait until all the queued operations are finished
* and lock the image, so you can start accessing it in a safe way.
*
* @p readOnly tells the image if the caller is going to modify the image during
* holding the lock. Locking with non-readOnly access will reset all
* the internal caches of the image (lod-planes) when the lock status
* will be lifted.
*/
void barrierLock(bool readOnly = false);
/**
* @brief Tries to lock the image without waiting for the jobs to finish
*
* Same as barrierLock(), but doesn't block execution of the calling thread
* until all the background jobs are finished. Instead, in case of presence of
* unfinished jobs in the queue, it just returns false
*
* @return whether the lock has been acquired
* @see barrierLock
*/
bool tryBarrierLock(bool readOnly = false);
/**
* Wait for all the internal image jobs to complete and return without locking
* the image. This function is handly for tests or other synchronous actions,
* when one needs to wait for the result of his actions.
*/
void waitForDone();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* @brief blockUpdates block updating the image projection
*/
void blockUpdates() override;
/**
* @brief unblockUpdates unblock updating the image project. This
* only restarts the scheduler and does not schedule a full refresh.
*/
void unblockUpdates() override;
/**
* Disables notification of the UI about the changes in the image.
* This feature is used by KisProcessingApplicator. It is needed
* when we change the size of the image. In this case, the whole
* image will be reloaded into UI by sigSizeChanged(), so there is
* no need to inform the UI about individual dirty rects.
*
* The last call to enableUIUpdates() will return the list of udpates
* that were requested while they were blocked.
*/
void disableUIUpdates() override;
/**
* Notify GUI about a bunch of updates planned. GUI is expected to wait
* until all the updates are completed, and render them on screen only
* in the very and of the batch.
*/
void notifyBatchUpdateStarted() override;
/**
* Notify GUI that batch update has been completed. Now GUI can start
* showing all of them on screen.
*/
void notifyBatchUpdateEnded() override;
/**
* Notify GUI that rect \p rc is now prepared in the image and
* GUI can read data from it.
*
* WARNING: GUI will read the data right in the handler of this
* signal, so exclusive access to the area must be guaranteed
* by the caller.
*/
void notifyUIUpdateCompleted(const QRect &rc) override;
/**
* \see disableUIUpdates
*/
QVector enableUIUpdates() override;
/**
* Disables the processing of all the setDirty() requests that
* come to the image. The incoming requests are effectively
* *dropped*.
*
* This feature is used by KisProcessingApplicator. For many cases
* it provides its own updates interface, which recalculates the
* whole subtree of nodes. But while we change any particular
* node, it can ask for an update itself. This method is a way of
* blocking such intermediate (and excessive) requests.
*
* NOTE: this is a convenience function for setProjectionUpdatesFilter()
* that installs a predefined filter that eats everything. Please
* note that these calls are *not* recursive
*/
void disableDirtyRequests() override;
/**
* \see disableDirtyRequests()
*/
void enableDirtyRequests() override;
/**
* Installs a filter object that will filter all the incoming projection update
* requests. If the filter return true, the incoming update is dropped.
*
* NOTE: you cannot set filters recursively!
*/
void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) override;
/**
* \see setProjectionUpdatesFilter()
*/
KisProjectionUpdatesFilterSP projectionUpdatesFilter() const override;
void refreshGraphAsync(KisNodeSP root = KisNodeSP()) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc) override;
void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) override;
/**
* Triggers synchronous recomposition of the projection
*/
void refreshGraph(KisNodeSP root = KisNodeSP());
void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void initialRefreshGraph();
/**
* Initiate a stack regeneration skipping the recalculation of the
* filthy node's projection.
*
* Works exactly as pseudoFilthy->setDirty() with the only
* exception that pseudoFilthy::updateProjection() will not be
* called. That is used by KisRecalculateTransformMaskJob to avoid
* cyclic dependencies.
*/
void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect);
/**
* Adds a spontaneous job to the updates queue.
*
* A spontaneous job may do some trivial tasks in the background,
* like updating the outline of selection or purging unused tiles
* from the existing paint devices.
*/
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
+ /**
+ * \return true if there are some updates in the updates queue
+ * Please note, that is doesn't guarantee that there are no updates
+ * running in in the updater context at the very moment. To guarantee that
+ * there are no updates left at all, please use barrier jobs instead.
+ */
+ bool hasUpdatesRunning() const override;
+
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks the current stroke should undo its last
* action, for example, when the user presses Ctrl+Z while some
* stroke is active.
*
* If the creator of the stroke supports undoing of intermediate
* actions, it will be notified about this request and can undo
* its last action.
*/
void requestUndoDuringStroke();
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks current stroke should be cancelled. If
* there is a running stroke that has already been detached from
* its creator (ended or cancelled), it will be forcefully
* cancelled and reverted. If there is an open stroke present, and
* if its creator supports cancelling, it will be notified about
* the request and the stroke will be cancelled
*/
void requestStrokeCancellation();
/**
* This method requests the last stroke executed on the image to become undone.
* If the stroke is not ended, or if all the Lod0 strokes are completed, the method
* returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT
* is returned and the caller should just wait for its completion and call global undo
* instead. UNDO_OK means one unfinished stroke has been undone.
*/
UndoResult tryUndoUnfinishedLod0Stroke();
/**
* This method is called when image or some other part of Krita
* (*not* the creator of the stroke) decides that the stroke
* should be ended. If the creator of the stroke supports it, it
* will be notified and the stroke will be cancelled
*/
void requestStrokeEnd();
/**
* Same as requestStrokeEnd() but is called by view manager when
* the current node is changed. Use to distinguish
* sigStrokeEndRequested() and
* sigStrokeEndRequestedActiveNodeFiltered() which are used by
* KisNodeJugglerCompressed
*/
void requestStrokeEndActiveNode();
private:
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
void resizeImageImpl(const QRect& newRect, bool cropLayers);
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, double radians,
bool resizeImage, KisSelectionSP selection);
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double angleX, double angleY, KisSelectionSP selection);
void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2);
void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea);
void requestProjectionUpdateImpl(KisNode *node,
const QVector &rects,
const QRect &cropRect);
friend class KisImageResizeCommand;
void setSize(const QSize& size);
friend class KisImageSetProjectionColorSpaceCommand;
void setProjectionColorSpace(const KoColorSpace * colorSpace);
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisImageTest;
friend class Document; // For libkis
/**
* Replaces the current global selection with globalSelection. If
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void deselectGlobalSelection();
/**
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
void reselectGlobalSelection();
private:
class KisImagePrivate;
KisImagePrivate * m_d;
};
#endif // KIS_IMAGE_H_
diff --git a/libs/image/kis_image_interfaces.h b/libs/image/kis_image_interfaces.h
index 4dc3387ca6..81c1465687 100644
--- a/libs/image/kis_image_interfaces.h
+++ b/libs/image/kis_image_interfaces.h
@@ -1,84 +1,86 @@
/*
* Copyright (c) 2011 Dmitry Kazakov
*
* 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.
*/
#ifndef __KIS_IMAGE_INTERFACES_H
#define __KIS_IMAGE_INTERFACES_H
#include "kis_types.h"
#include
class QRect;
class KisStrokeStrategy;
class KisStrokeJobData;
class KisPostExecutionUndoAdapter;
class KRITAIMAGE_EXPORT KisStrokesFacade
{
public:
virtual ~KisStrokesFacade();
virtual KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) = 0;
virtual void addJob(KisStrokeId id, KisStrokeJobData *data) = 0;
virtual void endStroke(KisStrokeId id) = 0;
virtual bool cancelStroke(KisStrokeId id) = 0;
};
class KRITAIMAGE_EXPORT KisUpdatesFacade
{
public:
virtual ~KisUpdatesFacade();
virtual void blockUpdates() = 0;
virtual void unblockUpdates() = 0;
virtual void disableUIUpdates() = 0;
virtual QVector enableUIUpdates() = 0;
+ virtual bool hasUpdatesRunning() const = 0;
+
virtual void notifyBatchUpdateStarted() = 0;
virtual void notifyBatchUpdateEnded() = 0;
virtual void notifyUIUpdateCompleted(const QRect &rc) = 0;
virtual QRect bounds() const = 0;
virtual void disableDirtyRequests() = 0;
virtual void enableDirtyRequests() = 0;
virtual void refreshGraphAsync(KisNodeSP root) = 0;
virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc) = 0;
virtual void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) = 0;
virtual void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) = 0;
virtual KisProjectionUpdatesFilterSP projectionUpdatesFilter() const = 0;
};
class KRITAIMAGE_EXPORT KisProjectionUpdateListener
{
public:
virtual ~KisProjectionUpdateListener();
virtual void notifyProjectionUpdated(const QRect &rc) = 0;
};
class KRITAIMAGE_EXPORT KisStrokeUndoFacade
{
public:
virtual ~KisStrokeUndoFacade();
virtual KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const = 0;
virtual const KUndo2Command* lastExecutedCommand() const = 0;
};
#endif /* __KIS_IMAGE_INTERFACES_H */
diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc
index 2a71588134..b07dd02001 100644
--- a/libs/image/kis_mask.cc
+++ b/libs/image/kis_mask.cc
@@ -1,511 +1,514 @@
/*
* Copyright (c) 2006 Boudewijn Rempt
* (c) 2009 Dmitry Kazakov
*
* 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 "kis_mask.h"
#include
// to prevent incomplete class types on "delete selection->flatten();"
#include
#include
#include
#include
#include
#include "kis_paint_device.h"
#include "kis_selection.h"
#include "kis_pixel_selection.h"
#include "kis_painter.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_cached_paint_device.h"
#include "kis_mask_projection_plane.h"
#include "kis_raster_keyframe_channel.h"
#include "KisSafeNodeProjectionStore.h"
struct Q_DECL_HIDDEN KisMask::Private {
Private(KisMask *_q)
: q(_q),
projectionPlane(new KisMaskProjectionPlane(q))
{
}
mutable KisSelectionSP selection;
KisCachedPaintDevice paintDeviceCache;
KisMask *q;
/**
* Due to the design of the Kra format the X,Y offset of the paint
* device belongs to the node, but not to the device itself. So
* the offset is set when the node is created, but not when the
* selection is initialized. This causes the X,Y values to be
* lost, since the selection doen not exist at the moment. That is
* why we save it separately.
*/
QScopedPointer deferredSelectionOffset;
KisAbstractProjectionPlaneSP projectionPlane;
KisSafeSelectionNodeProjectionStoreSP safeProjection;
void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice);
};
KisMask::KisMask(const QString & name)
: KisNode(nullptr)
, m_d(new Private(this))
{
setName(name);
m_d->safeProjection = new KisSafeSelectionNodeProjectionStore();
m_d->safeProjection->setImage(image());
}
KisMask::KisMask(const KisMask& rhs)
: KisNode(rhs)
, KisIndirectPaintingSupport()
, m_d(new Private(this))
{
setName(rhs.name());
m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(*rhs.m_d->safeProjection);
if (rhs.m_d->selection) {
m_d->selection = new KisSelection(*rhs.m_d->selection.data());
m_d->selection->setParentNode(this);
KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection();
if (pixelSelection->framesInterface()) {
addKeyframeChannel(pixelSelection->keyframeChannel());
enableAnimation();
}
}
}
KisMask::~KisMask()
{
if (m_d->selection) {
m_d->selection->setParentNode(0);
}
delete m_d;
}
void KisMask::setImage(KisImageWSP image)
{
KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0;
KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image);
if (m_d->selection) {
m_d->selection->setDefaultBounds(defaultBounds);
}
m_d->safeProjection->setImage(image);
KisNode::setImage(image);
}
bool KisMask::allowAsChild(KisNodeSP node) const
{
Q_UNUSED(node);
return false;
}
const KoColorSpace * KisMask::colorSpace() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->colorSpace() : 0;
}
const KoCompositeOp * KisMask::compositeOp() const
{
/**
* FIXME: This function duplicates the same function from
* KisLayer. We can't move it to KisBaseNode as it doesn't
* know anything about parent() method of KisNode
* Please think it over...
*/
const KoColorSpace *colorSpace = this->colorSpace();
if (!colorSpace) return 0;
const KoCompositeOp* op = colorSpace->compositeOp(compositeOpId());
return op ? op : colorSpace->compositeOp(COMPOSITE_OVER);
}
void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer)
{
m_d->initSelectionImpl(copyFrom, parentLayer, 0);
}
void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer)
{
m_d->initSelectionImpl(0, parentLayer, copyFromDevice);
}
void KisMask::initSelection(KisLayerSP parentLayer)
{
m_d->initSelectionImpl(0, parentLayer, 0);
}
void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice)
{
Q_ASSERT(parentLayer);
KisPaintDeviceSP parentPaintDevice = parentLayer->original();
if (copyFrom) {
/**
* We can't use setSelection as we may not have parent() yet
*/
selection = new KisSelection(*copyFrom);
selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
if (copyFrom->hasShapeSelection()) {
delete selection->flatten();
}
} else if (copyFromDevice) {
KritaUtils::DeviceCopyMode copyMode =
q->inherits("KisFilterMask") || q->inherits("KisTransparencyMask") ?
KritaUtils::CopyAllFrames : KritaUtils::CopySnapshot;
selection = new KisSelection(copyFromDevice, copyMode, new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
KisPixelSelectionSP pixelSelection = selection->pixelSelection();
if (pixelSelection->framesInterface()) {
- q->addKeyframeChannel(pixelSelection->keyframeChannel());
+ KisRasterKeyframeChannel *keyframeChannel = pixelSelection->keyframeChannel();
+ keyframeChannel->setFilenameSuffix(".pixelselection");
+
+ q->addKeyframeChannel(keyframeChannel);
q->enableAnimation();
}
} else {
selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image()));
selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, selection->pixelSelection()->colorSpace()));
if (deferredSelectionOffset) {
selection->setX(deferredSelectionOffset->x());
selection->setY(deferredSelectionOffset->y());
deferredSelectionOffset.reset();
}
}
selection->setParentNode(q);
selection->updateProjection();
}
KisSelectionSP KisMask::selection() const
{
return m_d->selection;
}
KisPaintDeviceSP KisMask::paintDevice() const
{
KisSelectionSP selection = this->selection();
return selection ? selection->pixelSelection() : 0;
}
KisPaintDeviceSP KisMask::original() const
{
return paintDevice();
}
KisPaintDeviceSP KisMask::projection() const
{
KisPaintDeviceSP originalDevice = original();
KisPaintDeviceSP result = originalDevice;
KisSelectionSP selection = this->selection();
if (selection && hasTemporaryTarget()) {
result = m_d->safeProjection->getDeviceLazy(selection)->pixelSelection();
}
return result;
}
KisAbstractProjectionPlaneSP KisMask::projectionPlane() const
{
return m_d->projectionPlane;
}
void KisMask::setSelection(KisSelectionSP selection)
{
m_d->selection = selection;
if (parent()) {
const KisLayer *parentLayer = qobject_cast(parent());
m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image()));
}
m_d->selection->setParentNode(this);
}
void KisMask::select(const QRect & rc, quint8 selectedness)
{
KisSelectionSP sel = selection();
KisPixelSelectionSP psel = sel->pixelSelection();
psel->select(rc, selectedness);
sel->updateProjection(rc);
}
QRect KisMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const
{
Q_UNUSED(src);
Q_UNUSED(dst);
Q_UNUSED(maskPos);
Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors");
return rc;
}
bool KisMask::paintsOutsideSelection() const
{
return false;
}
void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const
{
if (selection()) {
flattenSelectionProjection(m_d->selection, applyRect);
KisSelectionSP effectiveSelection = m_d->selection;
{
// Access temporary target under the lock held
KisIndirectPaintingSupport::ReadLocker l(this);
if (!paintsOutsideSelection()) {
// extent of m_d->selection should also be accessed under a lock,
// because it might be being merged in by the temporary target atm
QRect effectiveExtent = m_d->selection->selectedRect();
if (hasTemporaryTarget()) {
effectiveExtent |= temporaryTarget()->extent();
}
if(!effectiveExtent.intersects(applyRect)) {
return;
}
}
if (hasTemporaryTarget()) {
effectiveSelection = m_d->safeProjection->getDeviceLazy(m_d->selection);
KisPainter::copyAreaOptimized(applyRect.topLeft(),
m_d->selection->pixelSelection(),
effectiveSelection->pixelSelection(), applyRect);
KisPainter gc(effectiveSelection->pixelSelection());
setupTemporaryPainter(&gc);
gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect);
} else {
m_d->safeProjection->releaseDevice();
}
mergeInMaskInternal(projection, effectiveSelection, applyRect, needRect, maskPos);
}
} else {
mergeInMaskInternal(projection, 0, applyRect, needRect, maskPos);
}
}
void KisMask::mergeInMaskInternal(KisPaintDeviceSP projection,
KisSelectionSP effectiveSelection,
const QRect &applyRect,
const QRect &preparedNeedRect,
KisNode::PositionToFilthy maskPos) const
{
KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
if (effectiveSelection) {
QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos);
// masks don't have any compositioning
KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection);
} else {
cacheDevice->makeCloneFromRough(projection, preparedNeedRect);
projection->clear(preparedNeedRect);
decorateRect(cacheDevice, projection, applyRect, maskPos);
}
m_d->paintDeviceCache.putDevice(cacheDevice);
}
void KisMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const
{
selection->updateProjection(dirtyRect);
}
QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
QRect resultRect = rect;
if (m_d->selection) {
QRect selectionExtent = m_d->selection->selectedRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
selectionExtent |= temporaryTarget->extent();
}
resultRect &= selectionExtent;
}
return resultRect;
}
QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const
{
return KisMask::needRect(rect, pos);
}
QRect KisMask::extent() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->extent();
}
} else if (KisNodeSP parent = this->parent()) {
resultRect = parent->extent();
}
return resultRect;
}
QRect KisMask::exactBounds() const
{
QRect resultRect;
if (m_d->selection) {
resultRect = m_d->selection->selectedExactRect();
// copy for thread safety!
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
resultRect |= temporaryTarget->exactBounds();
}
} else if (KisNodeSP parent = this->parent()) {
resultRect = parent->exactBounds();
}
return resultRect;
}
qint32 KisMask::x() const
{
return m_d->selection ? m_d->selection->x() :
m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() :
parent() ? parent()->x() : 0;
}
qint32 KisMask::y() const
{
return m_d->selection ? m_d->selection->y() :
m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() :
parent() ? parent()->y() : 0;
}
void KisMask::setX(qint32 x)
{
if (m_d->selection) {
m_d->selection->setX(x);
} else if (!m_d->deferredSelectionOffset) {
m_d->deferredSelectionOffset.reset(new QPoint(x, 0));
} else {
m_d->deferredSelectionOffset->rx() = x;
}
}
void KisMask::setY(qint32 y)
{
if (m_d->selection) {
m_d->selection->setY(y);
} else if (!m_d->deferredSelectionOffset) {
m_d->deferredSelectionOffset.reset(new QPoint(0, y));
} else {
m_d->deferredSelectionOffset->ry() = y;
}
}
QRect KisMask::nonDependentExtent() const
{
return QRect();
}
QImage KisMask::createThumbnail(qint32 w, qint32 h)
{
KisPaintDeviceSP originalDevice =
selection() ? selection()->projection() : 0;
return originalDevice ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
void KisMask::testingInitSelection(const QRect &rect, KisLayerSP parentLayer)
{
if (parentLayer) {
m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice(), parentLayer->image()));
} else {
m_d->selection = new KisSelection();
}
m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8);
m_d->selection->updateProjection(rect);
m_d->selection->setParentNode(this);
}
KisKeyframeChannel *KisMask::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Content.id()) {
KisPaintDeviceSP device = paintDevice();
if (device) {
KisRasterKeyframeChannel *contentChannel = device->createKeyframeChannel(KisKeyframeChannel::Content);
contentChannel->setFilenameSuffix(".pixelselection");
return contentChannel;
}
}
return KisNode::requestKeyframeChannel(id);
}
void KisMask::baseNodeChangedCallback()
{
KisNodeSP up = parent();
KisLayer *layer = dynamic_cast(up.data());
if (layer) {
layer->notifyChildMaskChanged();
}
KisNode::baseNodeChangedCallback();
}
diff --git a/libs/image/kis_update_scheduler.cpp b/libs/image/kis_update_scheduler.cpp
index 0196cb19b5..ad3a760e59 100644
--- a/libs/image/kis_update_scheduler.cpp
+++ b/libs/image/kis_update_scheduler.cpp
@@ -1,492 +1,497 @@
/*
* Copyright (c) 2010 Dmitry Kazakov
*
* 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 "kis_update_scheduler.h"
#include "klocalizedstring.h"
#include "kis_image_config.h"
#include "kis_merge_walker.h"
#include "kis_full_refresh_walker.h"
#include "kis_updater_context.h"
#include "kis_simple_update_queue.h"
#include "kis_strokes_queue.h"
#include "kis_queues_progress_updater.h"
#include "KisImageConfigNotifier.h"
#include
#include "kis_lazy_wait_condition.h"
#include
//#define DEBUG_BALANCING
#ifdef DEBUG_BALANCING
#define DEBUG_BALANCING_METRICS(decidedFirst, excl) \
dbgKrita << "Balance decision:" << decidedFirst \
<< "(" << excl << ")" \
<< "updates:" << m_d->updatesQueue.sizeMetric() \
<< "strokes:" << m_d->strokesQueue.sizeMetric()
#else
#define DEBUG_BALANCING_METRICS(decidedFirst, excl)
#endif
struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p)
: q(_q)
, updaterContext(KisImageConfig(true).maxNumberOfThreads(), q)
, projectionUpdateListener(p)
{}
KisUpdateScheduler *q;
KisSimpleUpdateQueue updatesQueue;
KisStrokesQueue strokesQueue;
KisUpdaterContext updaterContext;
bool processingBlocked = false;
qreal defaultBalancingRatio = 1.0; // desired strokes-queue-size / updates-queue-size
KisProjectionUpdateListener *projectionUpdateListener;
KisQueuesProgressUpdater *progressUpdater = 0;
QAtomicInt updatesLockCounter;
QReadWriteLock updatesStartLock;
KisLazyWaitCondition updatesFinishedCondition;
qreal balancingRatio() const {
const qreal strokeRatioOverride = strokesQueue.balancingRatioOverride();
return strokeRatioOverride > 0 ? strokeRatioOverride : defaultBalancingRatio;
}
};
KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent)
: QObject(parent),
m_d(new Private(this, projectionUpdateListener))
{
updateSettings();
connectSignals();
}
KisUpdateScheduler::KisUpdateScheduler()
: m_d(new Private(this, 0))
{
}
KisUpdateScheduler::~KisUpdateScheduler()
{
delete m_d->progressUpdater;
delete m_d;
}
void KisUpdateScheduler::setThreadsLimit(int value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->processingBlocked);
/**
* Thread limit can be changed without the full-featured barrier
* lock, we can avoid waiting for all the jobs to complete. We
* should just ensure there is no more jobs in the updater context.
*/
lock();
m_d->updaterContext.lock();
m_d->updaterContext.setThreadsLimit(value);
m_d->updaterContext.unlock();
unlock(false);
}
int KisUpdateScheduler::threadsLimit() const
{
std::lock_guard l(m_d->updaterContext);
return m_d->updaterContext.threadsLimit();
}
void KisUpdateScheduler::connectSignals()
{
connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()),
SLOT(updateSettings()));
}
void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy)
{
delete m_d->progressUpdater;
m_d->progressUpdater = progressProxy ?
new KisQueuesProgressUpdater(progressProxy, this) : 0;
}
void KisUpdateScheduler::progressUpdate()
{
if (!m_d->progressUpdater) return;
if(!m_d->strokesQueue.hasOpenedStrokes()) {
QString jobName = m_d->strokesQueue.currentStrokeName().toString();
if(jobName.isEmpty()) {
jobName = i18n("Updating...");
}
int sizeMetric = m_d->strokesQueue.sizeMetric();
if (!sizeMetric) {
sizeMetric = m_d->updatesQueue.sizeMetric();
}
m_d->progressUpdater->updateProgress(sizeMetric, jobName);
}
else {
m_d->progressUpdater->hide();
}
}
void KisUpdateScheduler::updateProjection(KisNodeSP node, const QVector &rects, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateJob(node, rects, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addFullRefreshJob(root, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect);
walker->collectRects(root, rc);
bool needLock = true;
if(m_d->processingBlocked) {
warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held";
warnImage << "We will not assert for now, but please port caller's to strokes";
warnImage << "to avoid this warning";
needLock = false;
}
if(needLock) lock();
m_d->updaterContext.lock();
Q_ASSERT(m_d->updaterContext.isJobAllowed(walker));
m_d->updaterContext.addMergeJob(walker);
m_d->updaterContext.waitForDone();
m_d->updaterContext.unlock();
if(needLock) unlock(true);
}
void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->updatesQueue.addSpontaneousJob(spontaneousJob);
processQueues();
}
+bool KisUpdateScheduler::hasUpdatesRunning() const
+{
+ return !m_d->updatesQueue.isEmpty();
+}
+
KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy)
{
KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy);
processQueues();
return id;
}
void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data)
{
m_d->strokesQueue.addJob(id, data);
processQueues();
}
void KisUpdateScheduler::endStroke(KisStrokeId id)
{
m_d->strokesQueue.endStroke(id);
processQueues();
}
bool KisUpdateScheduler::cancelStroke(KisStrokeId id)
{
bool result = m_d->strokesQueue.cancelStroke(id);
processQueues();
return result;
}
bool KisUpdateScheduler::tryCancelCurrentStrokeAsync()
{
return m_d->strokesQueue.tryCancelCurrentStrokeAsync();
}
UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync()
{
return m_d->strokesQueue.tryUndoLastStrokeAsync();
}
bool KisUpdateScheduler::wrapAroundModeSupported() const
{
return m_d->strokesQueue.wrapAroundModeSupported();
}
void KisUpdateScheduler::setDesiredLevelOfDetail(int lod)
{
m_d->strokesQueue.setDesiredLevelOfDetail(lod);
/**
* The queue might have started an internal stroke for
* cache synchronization. Process the queues to execute
* it if needed.
*/
processQueues();
}
void KisUpdateScheduler::explicitRegenerateLevelOfDetail()
{
m_d->strokesQueue.explicitRegenerateLevelOfDetail();
// \see a comment in setDesiredLevelOfDetail()
processQueues();
}
int KisUpdateScheduler::currentLevelOfDetail() const
{
int levelOfDetail = m_d->updaterContext.currentLevelOfDetail();
if (levelOfDetail < 0) {
levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail();
}
if (levelOfDetail < 0) {
levelOfDetail = 0;
}
return levelOfDetail;
}
void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
{
m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setSuspendUpdatesStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setResumeUpdatesStrokeStrategyFactory(factory);
}
KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const
{
return m_d->strokesQueue.lodNPostExecutionUndoAdapter();
}
void KisUpdateScheduler::updateSettings()
{
m_d->updatesQueue.updateSettings();
KisImageConfig config(true);
m_d->defaultBalancingRatio = config.schedulerBalancingRatio();
setThreadsLimit(config.maxNumberOfThreads());
}
void KisUpdateScheduler::lock()
{
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
}
void KisUpdateScheduler::unlock(bool resetLodLevels)
{
if (resetLodLevels) {
/**
* Legacy strokes may have changed the image while we didn't
* control it. Notify the queue to take it into account.
*/
m_d->strokesQueue.notifyUFOChangedImage();
}
m_d->processingBlocked = false;
processQueues();
}
bool KisUpdateScheduler::isIdle()
{
bool result = false;
if (tryBarrierLock()) {
result = true;
unlock(false);
}
return result;
}
void KisUpdateScheduler::waitForDone()
{
do {
processQueues();
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
bool KisUpdateScheduler::tryBarrierLock()
{
if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
return false;
}
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
m_d->processingBlocked = false;
processQueues();
return false;
}
return true;
}
void KisUpdateScheduler::barrierLock()
{
do {
m_d->processingBlocked = false;
processQueues();
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
void KisUpdateScheduler::processQueues()
{
wakeUpWaitingThreads();
if(m_d->processingBlocked) return;
if(m_d->strokesQueue.needsExclusiveAccess()) {
DEBUG_BALANCING_METRICS("STROKES", "X");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
if(!m_d->strokesQueue.needsExclusiveAccess()) {
tryProcessUpdatesQueue();
}
}
else if(m_d->balancingRatio() * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
DEBUG_BALANCING_METRICS("STROKES", "N");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
tryProcessUpdatesQueue();
}
else {
DEBUG_BALANCING_METRICS("UPDATES", "N");
tryProcessUpdatesQueue();
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
}
progressUpdate();
}
void KisUpdateScheduler::blockUpdates()
{
m_d->updatesFinishedCondition.initWaiting();
m_d->updatesLockCounter.ref();
while(haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wait();
}
m_d->updatesFinishedCondition.endWaiting();
}
void KisUpdateScheduler::unblockUpdates()
{
m_d->updatesLockCounter.deref();
processQueues();
}
void KisUpdateScheduler::wakeUpWaitingThreads()
{
if(m_d->updatesLockCounter && !haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wakeAll();
}
}
void KisUpdateScheduler::tryProcessUpdatesQueue()
{
QReadLocker locker(&m_d->updatesStartLock);
if(m_d->updatesLockCounter) return;
m_d->updatesQueue.processQueue(m_d->updaterContext);
}
bool KisUpdateScheduler::haveUpdatesRunning()
{
QWriteLocker locker(&m_d->updatesStartLock);
qint32 numMergeJobs, numStrokeJobs;
m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
return numMergeJobs;
}
void KisUpdateScheduler::continueUpdate(const QRect &rect)
{
Q_ASSERT(m_d->projectionUpdateListener);
m_d->projectionUpdateListener->notifyProjectionUpdated(rect);
}
void KisUpdateScheduler::doSomeUsefulWork()
{
m_d->updatesQueue.optimize();
}
void KisUpdateScheduler::spareThreadAppeared()
{
processQueues();
}
KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
qint32 threadCount)
{
Q_UNUSED(threadCount);
updateSettings();
m_d->projectionUpdateListener = projectionUpdateListener;
// The queue will update settings in a constructor itself
// m_d->updatesQueue = new KisTestableSimpleUpdateQueue();
// m_d->strokesQueue = new KisStrokesQueue();
connectSignals();
}
KisUpdaterContext *KisTestableUpdateScheduler::updaterContext()
{
return &m_d->updaterContext;
}
KisTestableSimpleUpdateQueue* KisTestableUpdateScheduler::updateQueue()
{
return dynamic_cast(&m_d->updatesQueue);
}
diff --git a/libs/image/kis_update_scheduler.h b/libs/image/kis_update_scheduler.h
index 477298a342..24b5a247d2 100644
--- a/libs/image/kis_update_scheduler.h
+++ b/libs/image/kis_update_scheduler.h
@@ -1,252 +1,254 @@
/*
* Copyright (c) 2010 Dmitry Kazakov
*
* 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.
*/
#ifndef __KIS_UPDATE_SCHEDULER_H
#define __KIS_UPDATE_SCHEDULER_H
#include
#include "kritaimage_export.h"
#include "kis_types.h"
#include "kis_image_interfaces.h"
#include "kis_stroke_strategy_factory.h"
#include "kis_strokes_queue_undo_result.h"
class QRect;
class KoProgressProxy;
class KisProjectionUpdateListener;
class KisSpontaneousJob;
class KisPostExecutionUndoAdapter;
class KRITAIMAGE_EXPORT KisUpdateScheduler : public QObject, public KisStrokesFacade
{
Q_OBJECT
public:
KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener, QObject *parent = 0);
~KisUpdateScheduler() override;
/**
* Set the number of threads used by the scheduler
*/
void setThreadsLimit(int value);
/**
* Return the number of threads available to the scheduler
*/
int threadsLimit() const;
/**
* Sets the proxy that is going to be notified about the progress
* of processing of the queues. If you want to switch the proxy
* on runtime, you should do it under the lock held.
*
* \see lock(), unlock()
*/
void setProgressProxy(KoProgressProxy *progressProxy);
/**
* Blocks processing of the queues.
* The function will wait until all the executing jobs
* are finished.
* NOTE: you may add new jobs while the block held, but they
* will be delayed until unlock() is called.
*
* \see unlock()
*/
void lock();
/**
* Unblocks the process and calls processQueues()
*
* \see processQueues()
*/
void unlock(bool resetLodLevels = true);
/**
* Waits until all the running jobs are finished.
*
* If some other thread adds jobs in parallel, then you may
* wait forever. If you you don't want it, consider lock() instead.
*
* \see lock()
*/
void waitForDone();
/**
* Waits until the queues become empty, then blocks the processing.
* To unblock processing you should use unlock().
*
* If some other thread adds jobs in parallel, then you may
* wait forever. If you you don't want it, consider lock() instead.
*
* \see unlock(), lock()
*/
void barrierLock();
/**
* Works like barrier lock, but returns false immediately if barrierLock
* can't be acquired.
*
* \see barrierLock()
*/
bool tryBarrierLock();
/**
* Tells if there are no strokes or updates are running at the
* moment. Internally calls to tryBarrierLock(), so it is not O(1).
*/
bool isIdle();
/**
* Blocks all the updates from execution. It doesn't affect
* strokes execution in any way. This type of lock is supposed
* to be held by the strokes themselves when they need a short
* access to some parts of the projection of the image.
*
* From all the other places you should use usual lock()/unlock()
* methods
*
* \see lock(), unlock()
*/
void blockUpdates();
/**
* Unblocks updates from execution previously locked by blockUpdates()
*
* \see blockUpdates()
*/
void unblockUpdates();
void updateProjection(KisNodeSP node, const QVector &rects, const QRect &cropRect);
void updateProjection(KisNodeSP node, const QRect &rc, const QRect &cropRect);
void updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect);
void fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
+ bool hasUpdatesRunning() const;
+
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override;
void addJob(KisStrokeId id, KisStrokeJobData *data) override;
void endStroke(KisStrokeId id) override;
bool cancelStroke(KisStrokeId id) override;
/**
* Sets the desired level of detail on which the strokes should
* work. Please note that this configuration will be applied
* starting from the next stroke. Please also note that this value
* is not guaranteed to coincide with the one returned by
* currentLevelOfDetail()
*/
void setDesiredLevelOfDetail(int lod);
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
/**
* Install a factory of a stroke strategy, that will be started
* every time when the scheduler needs to synchronize LOD caches
* of all the paint devices of the image.
*/
void setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory);
/**
* Install a factory of a stroke strategy, that will be started
* every time when the scheduler needs to postpone all the updates
* of the *LOD0* strokes.
*/
void setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
/**
* \see setSuspendUpdatesStrokeStrategyFactory()
*/
void setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory);
KisPostExecutionUndoAdapter* lodNPostExecutionUndoAdapter() const;
/**
* tryCancelCurrentStrokeAsync() checks whether there is a
* *running* stroke (which is being executed at this very moment)
* which is not still open by the owner (endStroke() or
* cancelStroke() have already been called) and cancels it.
*
* \return true if some stroke has been found and cancelled
*
* \note This method is *not* part of KisStrokesFacade! It is too
* low level for KisImage. In KisImage it is combined with
* more high level requestStrokeCancellation().
*/
bool tryCancelCurrentStrokeAsync();
UndoResult tryUndoLastStrokeAsync();
bool wrapAroundModeSupported() const;
int currentLevelOfDetail() const;
void continueUpdate(const QRect &rect);
void doSomeUsefulWork();
void spareThreadAppeared();
protected:
// Trivial constructor for testing support
KisUpdateScheduler();
void connectSignals();
void processQueues();
protected Q_SLOTS:
/**
* Called when it is necessary to reread configuration
*/
void updateSettings();
private:
friend class UpdatesBlockTester;
bool haveUpdatesRunning();
void tryProcessUpdatesQueue();
void wakeUpWaitingThreads();
void progressUpdate();
protected:
struct Private;
Private * const m_d;
};
class KisTestableSimpleUpdateQueue;
class KisUpdaterContext;
class KRITAIMAGE_EXPORT KisTestableUpdateScheduler : public KisUpdateScheduler
{
public:
KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
qint32 threadCount);
KisUpdaterContext* updaterContext();
KisTestableSimpleUpdateQueue* updateQueue();
using KisUpdateScheduler::processQueues;
};
#endif /* __KIS_UPDATE_SCHEDULER_H */
diff --git a/libs/libqml/KisSketchView.cpp b/libs/libqml/KisSketchView.cpp
index b606964ab7..0910d6ee8f 100644
--- a/libs/libqml/KisSketchView.cpp
+++ b/libs/libqml/KisSketchView.cpp
@@ -1,676 +1,673 @@
/* This file is part of the KDE project
* Copyright (C) 2012 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 "KisSketchView.h"
#include
#include
#include
#include
#include
#include
#include "KisDocument.h"
#include "kis_canvas2.h"
#include
#include "KisViewManager.h"
#include
#include
#include
#include
#include
#include
#include
#include "KisSelectionExtras.h"
#include "ProgressProxy.h"
#include "DocumentManager.h"
class KisSketchView::Private
{
public:
Private( KisSketchView* qq)
: q(qq)
, actionCollection(0)
, doc(0)
, viewManager(0)
, view(0)
, canvas(0)
, canvasWidget(0)
, selectionExtras(0)
, undoAction(0)
, redoAction(0)
, tabletEventCount(0)
{ }
~Private() {
delete selectionExtras;
}
void imageUpdated(const QRect &updated);
void documentOffsetMoved();
void zoomChanged();
void resetDocumentPosition();
void removeNodeAsync(KisNodeSP removedNode);
KisSketchView* q;
KActionCollection *actionCollection;
QPointer doc;
QPointer viewManager;
QPointer view;
QPointer canvas;
KUndo2Stack* undoStack;
QWidget *canvasWidget;
QString file;
KisSelectionExtras *selectionExtras;
QTimer *timer;
QTimer *loadedTimer;
QTimer *savedTimer;
QAction* undoAction;
QAction* redoAction;
unsigned char tabletEventCount;
};
KisSketchView::KisSketchView(QQuickItem* parent)
: QQuickItem(parent)
, d(new Private(this))
{
// this is just an interaction overlay, the contents are painted on the sceneview background
setFlag(QQuickItem::ItemHasContents, false);
// QT5TODO
// setAcceptTouchEvents(true);
setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
setAcceptHoverEvents(true);
d->actionCollection = new KActionCollection(this, "krita");
d->viewManager = 0; //new KisViewManager(qApp->activeWindow(), d->actionCollection);
// QT5TODO
// grabGesture(Qt::PanGesture);
//grabGesture(Qt::PinchGesture);
KoZoomMode::setMinimumZoom(0.1);
KoZoomMode::setMaximumZoom(16.0);
d->timer = new QTimer(this);
d->timer->setSingleShot(true);
connect(d->timer, SIGNAL(timeout()), this, SLOT(resetDocumentPosition()));
d->loadedTimer = new QTimer(this);
d->loadedTimer->setSingleShot(true);
d->loadedTimer->setInterval(100);
connect(d->loadedTimer, SIGNAL(timeout()), SIGNAL(loadingFinished()));
d->savedTimer = new QTimer(this);
d->savedTimer->setSingleShot(true);
d->savedTimer->setInterval(100);
connect(d->savedTimer, SIGNAL(timeout()), SIGNAL(savingFinished()));
connect(DocumentManager::instance(), SIGNAL(aboutToDeleteDocument()), SLOT(documentAboutToBeDeleted()));
connect(DocumentManager::instance(), SIGNAL(documentChanged()), SLOT(documentChanged()));
connect(DocumentManager::instance()->progressProxy(), SIGNAL(valueChanged(int)), SIGNAL(progress(int)));
connect(DocumentManager::instance(), SIGNAL(documentSaved()), d->savedTimer, SLOT(start()));
if (DocumentManager::instance()->document()) {
documentChanged();
}
}
KisSketchView::~KisSketchView()
{
if (d->doc) {
DocumentManager::instance()->closeDocument();
}
if (d->canvasWidget) {
// QT5TODO
// SketchDeclarativeView *v = qobject_cast(scene()->views().at(0));
// if (v) {
// v->setCanvasWidget(0);
// v->setDrawCanvas(false);
// }
}
delete d;
}
QObject* KisSketchView::selectionManager() const
{
if (!d->viewManager)
return 0;
return d->viewManager->selectionManager();
}
QObject* KisSketchView::selectionExtras() const
{
if (!d->selectionExtras) {
d->selectionExtras = new KisSelectionExtras(d->viewManager);
}
return d->selectionExtras;
}
QObject* KisSketchView::doc() const
{
return d->doc;
}
QObject* KisSketchView::view() const
{
return d->viewManager;
}
QString KisSketchView::file() const
{
return d->file;
}
QString KisSketchView::fileTitle() const
{
QFileInfo file(d->file);
return file.fileName();
}
bool KisSketchView::isModified() const
{
if(d->doc)
return d->doc->isModified();
return false;
}
void KisSketchView::setFile(const QString& file)
{
if (!file.isEmpty() && file != d->file) {
d->file = file;
emit fileChanged();
if (!file.startsWith("temp://")) {
DocumentManager::instance()->openDocument(file);
}
}
}
void KisSketchView::componentComplete()
{
}
bool KisSketchView::canUndo() const
{
if (d->undoAction)
return d->undoAction->isEnabled();
return false;
}
bool KisSketchView::canRedo() const
{
if (d->redoAction)
return d->redoAction->isEnabled();
return false;
}
int KisSketchView::imageHeight() const
{
if (d->doc)
return d->doc->image()->height();
return 0;
}
int KisSketchView::imageWidth() const
{
if (d->doc)
return d->doc->image()->width();
return 0;
}
void KisSketchView::undo()
{
d->undoAction->trigger();
}
void KisSketchView::redo()
{
d->redoAction->trigger();
}
void KisSketchView::zoomIn()
{
d->viewManager->actionCollection()->action("zoom_in")->trigger();
}
void KisSketchView::zoomOut()
{
d->viewManager->actionCollection()->action("zoom_out")->trigger();
}
void KisSketchView::save()
{
DocumentManager::instance()->save();
}
void KisSketchView::saveAs(const QString& fileName, const QString& mimeType)
{
DocumentManager::instance()->saveAs(fileName, mimeType);
}
void KisSketchView::documentAboutToBeDeleted()
{
if (d->undoAction)
d->undoAction->disconnect(this);
if (d->redoAction)
d->redoAction->disconnect(this);
delete d->view;
d->view = 0;
emit viewChanged();
d->canvas = 0;
d->canvasWidget = 0;
}
void KisSketchView::documentChanged()
{
d->doc = DocumentManager::instance()->document();
if (!d->doc) return;
if (!d->viewManager) return;
connect(d->doc, SIGNAL(modified(bool)), SIGNAL(modifiedChanged()));
- QPointer view = qobject_cast(KisPart::instance()->createView(d->doc,
- d->viewManager->canvasResourceProvider()->resourceManager(),
- d->viewManager->actionCollection(),
- QApplication::activeWindow()));
+ QPointer view = KisPart::instance()->createView(d->doc, d->viewManager, QApplication::activeWindow());
view->setViewManager(d->viewManager);
view->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
view->slotLoadingFinished();
d->view = view;
d->canvas = d->view->canvasBase();
d->view->setShowFloatingMessage(false);
d->viewManager->setCurrentView(view);
KisCanvasController *controller = static_cast(d->canvas->canvasController());
connect(d->viewManager, SIGNAL(floatingMessageRequested(QString,QString)), this, SIGNAL(floatingMessageRequested(QString,QString)));
controller->setGeometry(x(), y(), width(), height());
d->view->hide();
d->undoStack = d->doc->undoStack();
d->undoAction = d->viewManager->actionCollection()->action("edit_undo");
connect(d->undoAction, SIGNAL(changed()), this, SIGNAL(canUndoChanged()));
d->redoAction = d->viewManager->actionCollection()->action("edit_redo");
connect(d->redoAction, SIGNAL(changed()), this, SIGNAL(canRedoChanged()));
KoToolManager::instance()->switchToolRequested( "KritaShape/KisToolBrush" );
d->canvasWidget = d->canvas->canvasWidget();
connect(d->doc->image(), SIGNAL(sigImageUpdated(QRect)), SLOT(imageUpdated(QRect)));
connect(controller->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved()));
connect(d->view->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), SLOT(zoomChanged()));
connect(d->canvas, SIGNAL(updateCanvasRequested(QRect)), SLOT(imageUpdated(QRect)));
connect(d->doc->image()->signalRouter(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(removeNodeAsync(KisNodeSP)));
connect(d->doc->image()->signalRouter(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SIGNAL(imageSizeChanged()));
// QT5TODO
// if(scene()) {
// SketchDeclarativeView *v = qobject_cast(scene()->views().at(0));
// if (v) {
// v->setCanvasWidget(d->canvasWidget);
// v->setDrawCanvas(true);
// }
// }
d->imageUpdated(d->canvas->image()->bounds());
static_cast(d->canvas->viewConverter())->setResolution(d->doc->image()->xRes(), d->doc->image()->yRes());
d->view->zoomController()->setZoomMode(KoZoomMode::ZOOM_PAGE);
controller->setScrollBarValue(QPoint(0, 0));
controller->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
controller->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
geometryChanged(QRectF(x(), y(), width(), height()), QRectF());
d->loadedTimer->start(100);
d->viewManager->actionCollection()->action("zoom_to_100pct")->trigger();
d->resetDocumentPosition();
emit viewChanged();
}
bool KisSketchView::event( QEvent* event )
{
if (!d->viewManager) return false;
if (!d->viewManager->canvasBase()) return false;
KisCanvasController *controller = dynamic_cast(d->viewManager->canvasBase()->canvasController());
if (!controller) return false;
// switch(static_cast(event->type())) {
// case ViewModeSwitchEvent::AboutToSwitchViewModeEvent: {
// ViewModeSynchronisationObject* syncObject = static_cast(event)->synchronisationObject();
// if (d->view && d->viewManager && d->viewManager->canvasBase()) {
// controller->setFocus();
// qApp->processEvents();
// KisCanvasResourceProvider* provider = d->view->resourceProvider();
// syncObject->backgroundColor = provider->bgColor();
// syncObject->foregroundColor = provider->fgColor();
// syncObject->exposure = provider->HDRExposure();
// syncObject->gamma = provider->HDRGamma();
// syncObject->compositeOp = provider->currentCompositeOp();
// syncObject->pattern = provider->currentPattern();
// syncObject->gradient = provider->currentGradient();
// syncObject->node = provider->currentNode();
// syncObject->paintOp = provider->currentPreset();
// syncObject->opacity = provider->opacity();
// syncObject->globalAlphaLock = provider->globalAlphaLock();
// syncObject->documentOffset = controller->scrollBarValue();
// syncObject->zoomLevel = d->view->zoomController()->zoomAction()->effectiveZoom();
// syncObject->rotationAngle = d->view->canvasBase()->rotationAngle();
// syncObject->activeToolId = KoToolManager::instance()->activeToolId();
// syncObject->gridConfig = d->view->document()->gridConfig();
// syncObject->mirrorHorizontal = provider->mirrorHorizontal();
// syncObject->mirrorVertical = provider->mirrorVertical();
// //syncObject->mirrorAxesCenter = provider->resourceManager()->resource(KisCanvasResourceProvider::MirrorAxesCenter).toPointF();
// KisToolFreehand* tool = qobject_cast(KoToolManager::instance()->toolById(d->view->canvasBase(), syncObject->activeToolId));
// if(tool) {
// syncObject->smoothingOptions = tool->smoothingOptions();
// }
// syncObject->initialized = true;
// }
// return true;
// }
// case ViewModeSwitchEvent::SwitchedToSketchModeEvent: {
// ViewModeSynchronisationObject* syncObject = static_cast(event)->synchronisationObject();
// if (d->view && syncObject->initialized) {
// controller->setFocus();
// qApp->processEvents();
// KisToolFreehand* tool = qobject_cast(KoToolManager::instance()->toolById(d->view->canvasBase(), syncObject->activeToolId));
// if(tool && syncObject->smoothingOptions) {
// tool->smoothingOptions()->setSmoothingType(syncObject->smoothingOptions->smoothingType());
// tool->smoothingOptions()->setSmoothPressure(syncObject->smoothingOptions->smoothPressure());
// tool->smoothingOptions()->setTailAggressiveness(syncObject->smoothingOptions->tailAggressiveness());
// tool->smoothingOptions()->setUseScalableDistance(syncObject->smoothingOptions->useScalableDistance());
// tool->smoothingOptions()->setSmoothnessDistance(syncObject->smoothingOptions->smoothnessDistance());
// tool->smoothingOptions()->setUseDelayDistance(syncObject->smoothingOptions->useDelayDistance());
// tool->smoothingOptions()->setDelayDistance(syncObject->smoothingOptions->delayDistance());
// tool->smoothingOptions()->setFinishStabilizedCurve(syncObject->smoothingOptions->finishStabilizedCurve());
// tool->smoothingOptions()->setStabilizeSensors(syncObject->smoothingOptions->stabilizeSensors());
// tool->updateSettingsViews();
// }
// KisCanvasResourceProvider* provider = d->view->resourceProvider();
// provider->setMirrorHorizontal(syncObject->mirrorHorizontal);
// provider->setMirrorVertical(syncObject->mirrorVertical);
// //provider->resourceManager()->setResource(KisCanvasResourceProvider::MirrorAxesCenter, syncObject->mirrorAxesCenter);
// provider->setPaintOpPreset(syncObject->paintOp);
// qApp->processEvents();
// KoToolManager::instance()->switchToolRequested("InteractionTool");
// qApp->processEvents();
// KoToolManager::instance()->switchToolRequested(syncObject->activeToolId);
// qApp->processEvents();
// provider->setBGColor(syncObject->backgroundColor);
// provider->setFGColor(syncObject->foregroundColor);
// provider->setHDRExposure(syncObject->exposure);
// provider->setHDRGamma(syncObject->gamma);
// provider->slotPatternActivated(syncObject->pattern);
// provider->slotGradientActivated(syncObject->gradient);
// provider->slotNodeActivated(syncObject->node);
// provider->setOpacity(syncObject->opacity);
// provider->setGlobalAlphaLock(syncObject->globalAlphaLock);
// provider->setCurrentCompositeOp(syncObject->compositeOp);
// d->view->document()->setGridConfig(syncObject->gridConfig);
// zoomIn();
// qApp->processEvents();
// d->view->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, syncObject->zoomLevel);
// controller->rotateCanvas(syncObject->rotationAngle - d->view->canvasBase()->rotationAngle());
// qApp->processEvents();
// QPoint newOffset = syncObject->documentOffset;
// controller->setScrollBarValue(newOffset);
// }
// return true;
// }
//#ifdef Q_OS_X11
// if(d->tabletEventCount % 2 == 0)
//#endif
// d->canvas->globalInputManager()->eventFilter(this, event);
// return true;
// case QEvent::KeyPress:
// case QEvent::KeyRelease:
// emit interactionStarted();
// QApplication::sendEvent(d->view, event);
// break;
// default:
// break;
// }
return QQuickItem::event( event );
}
// QT5TODO
#if 0
bool KisSketchView::sceneEvent(QEvent* event)
{
if (d->canvas && d->canvasWidget) {
switch(event->type()) {
case QEvent::GraphicsSceneMousePress: {
QGraphicsSceneMouseEvent *gsmevent = static_cast(event);
QMouseEvent mevent(QMouseEvent::MouseButtonPress, gsmevent->pos().toPoint(), gsmevent->button(), gsmevent->buttons(), gsmevent->modifiers());
QApplication::sendEvent(d->canvasWidget, &mevent);
emit interactionStarted();
return true;
}
case QEvent::GraphicsSceneMouseMove: {
QGraphicsSceneMouseEvent *gsmevent = static_cast(event);
QMouseEvent mevent(QMouseEvent::MouseMove, gsmevent->pos().toPoint(), gsmevent->button(), gsmevent->buttons(), gsmevent->modifiers());
QApplication::sendEvent(d->canvasWidget, &mevent);
update();
emit interactionStarted();
return true;
}
case QEvent::GraphicsSceneMouseRelease: {
QGraphicsSceneMouseEvent *gsmevent = static_cast(event);
QMouseEvent mevent(QMouseEvent::MouseButtonRelease, gsmevent->pos().toPoint(), gsmevent->button(), gsmevent->buttons(), gsmevent->modifiers());
QApplication::sendEvent(d->canvasWidget, &mevent);
emit interactionStarted();
return true;
}
case QEvent::GraphicsSceneWheel: {
QGraphicsSceneWheelEvent *gswevent = static_cast(event);
QWheelEvent wevent(gswevent->pos().toPoint(), gswevent->delta(), gswevent->buttons(), gswevent->modifiers(), gswevent->orientation());
QApplication::sendEvent(d->canvasWidget, &wevent);
emit interactionStarted();
return true;
}
case QEvent::GraphicsSceneHoverEnter: {
QGraphicsSceneHoverEvent *hevent = static_cast(event);
QHoverEvent e(QEvent::Enter, hevent->screenPos(), hevent->lastScreenPos());
QApplication::sendEvent(d->canvasWidget, &e);
return true;
}
case QEvent::GraphicsSceneHoverLeave: {
QGraphicsSceneHoverEvent *hevent = static_cast(event);
QHoverEvent e(QEvent::Leave, hevent->screenPos(), hevent->lastScreenPos());
QApplication::sendEvent(d->canvasWidget, &e);
return true;
}
case QEvent::TouchBegin: {
QApplication::sendEvent(d->canvasWidget, event);
event->accept();
emit interactionStarted();
return true;
}
case QEvent::TabletPress:
case QEvent::TabletMove:
case QEvent::TabletRelease:
d->canvas->globalInputManager()->stopIgnoringEvents();
QApplication::sendEvent(d->canvasWidget, event);
return true;
default:
if (QApplication::sendEvent(d->canvasWidget, event)) {
emit interactionStarted();
return true;
}
}
}
return QQuickItem::sceneEvent(event);
}
#endif
void KisSketchView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry)
{
if (d->canvasWidget && !newGeometry.isEmpty()) {
d->view->resize(newGeometry.toRect().size());
// If we don't ask for this event to be sent, the view does not actually handle
// the resize, and we're stuck with a very oddly sized viewport
QResizeEvent *event = new QResizeEvent(newGeometry.toRect().size(), d->view->size());
QApplication::sendEvent(d->view, event);
// This is a touch on the hackish side - i'm sure there's a better way of doing it
// but it's taking a long time to work it out. Problem: When switching orientation,
// the canvas is rendered wrong, in what looks like an off-by-one ish kind of fashion.
if (oldGeometry.height() == oldGeometry.width() && oldGeometry.height() == newGeometry.width()) {
// in this case, we've just rotated the display... do something useful!
// Turns out we get /two/ resize events per rotation, one one per setting each height and width.
// So we can't just check it normally. Annoying, but there you go.
QTimer::singleShot(100, this, SLOT(centerDoc()));
QTimer::singleShot(150, this, SLOT(zoomOut()));
}
if (oldGeometry.height() == oldGeometry.width() && oldGeometry.width() == newGeometry.height()) {
// in this case, we've just rotated the display... do something useful!
// Turns out we get /two/ resize events per rotation, one one per setting each height and width.
// So we can't just check it normally. Annoying, but there you go.
QTimer::singleShot(100, this, SLOT(centerDoc()));
QTimer::singleShot(150, this, SLOT(zoomOut()));
}
}
}
void KisSketchView::centerDoc()
{
d->viewManager->zoomController()->setZoom(KoZoomMode::ZOOM_PAGE, 1.0);
}
void KisSketchView::Private::imageUpdated(const QRect &/*updated*/)
{
// QT5TODO
// if (q->scene()) {
// q->scene()->views().at(0)->update(updated);
// q->scene()->invalidate( 0, 0, q->width(), q->height() );
// }
}
void KisSketchView::Private::documentOffsetMoved()
{
// QT5TODO
// if (q->scene()) {
// q->scene()->views().at(0)->update();
// q->scene()->invalidate( 0, 0, q->width(), q->height() );
// }
}
void KisSketchView::Private::resetDocumentPosition()
{
viewManager->zoomController()->setZoomMode(KoZoomMode::ZOOM_PAGE);
QPoint pos;
KisCanvasController *controller = dynamic_cast(viewManager->canvasBase()->canvasController());
if (!controller) return;
QScrollBar *sb = controller->horizontalScrollBar();
pos.rx() = sb->minimum() + (sb->maximum() - sb->minimum()) / 2;
sb = controller->verticalScrollBar();
pos.ry() = sb->minimum() + (sb->maximum() - sb->minimum()) / 2;
controller->setScrollBarValue(pos);
}
void KisSketchView::Private::removeNodeAsync(KisNodeSP removedNode)
{
if (removedNode) {
imageUpdated(removedNode->extent());
}
}
void KisSketchView::Private::zoomChanged()
{
// QT5TODO
// if (q->scene()) {
// q->scene()->views().at(0)->update();
// q->scene()->invalidate( 0, 0, q->width(), q->height() );
// }
}
void KisSketchView::activate()
{
if (d->canvasWidget != d->canvas->canvasWidget()) {
d->canvasWidget = d->canvas->canvasWidget();
// QT5TODO
// SketchDeclarativeView *v = qobject_cast(scene()->views().at(0));
// if (v) {
// v->setCanvasWidget(d->canvasWidget);
// v->setDrawCanvas(true);
// }
}
d->canvasWidget->setFocus();
Q_ASSERT(d->viewManager);
KisCanvasController *controller = dynamic_cast(d->viewManager->canvasBase()->canvasController());
Q_ASSERT(controller);
controller->activate();
}
// for private slots
#include "moc_KisSketchView.cpp"
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index a8f60ce179..0148cabd7d 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,615 +1,617 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
canvas/KisSnapPixelStrategy.cpp
canvas/KisMirrorAxisConfig.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
dialogs/KisDlgChangeCloneSource.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
KisPaintopPropertiesBase.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
KisOcioConfiguration.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
KisNodeDisplayModeAdapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
KisSelectionActionsAdapter.cpp
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
KisWelcomePageWidget.cpp
KisChangeCloneLayersCommand.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
opengl/KisOpenGLUpdateInfoBuilder.cpp
opengl/KisOpenGLModeProber.cpp
opengl/KisScreenInformationAdapter.cpp
kis_fps_decoration.cpp
tool/KisToolChangesTracker.cpp
tool/KisToolChangesTrackerData.cpp
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
+ tool/KisAsyncronousStrokeUpdateHelper.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
tool/strokes/move_stroke_strategy.cpp
tool/KisSelectionToolFactoryBase.cpp
tool/KisToolPaintFactoryBase.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KisScreenColorPicker.cpp
widgets/KoDualColorButton.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KisLayerStyleAngleSelector.cpp
widgets/KisMemoryReportButton.cpp
widgets/KisDitherWidget.cpp
KisPaletteEditor.cpp
dialogs/KisDlgPaletteEditor.cpp
widgets/KisNewsWidget.cpp
widgets/KisGamutMaskToolbar.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
utils/KisSpinBoxSplineUnitConverter.cpp
utils/KisClipboardUtil.cpp
utils/KisDitherUtil.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
input/kis_zoom_and_rotate_action.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactories.cpp
actions/KisTransformToolActivationCommand.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisCloneDocumentStroke.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
+ KisCanvasWindow.cpp
KisImportExportErrorCode.cpp
KisImportExportAdditionalChecks.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
KisMouseClickEater.cpp
)
if(WIN32)
# Private headers are needed for:
# * KisDlgCustomTabletResolution
# * KisScreenInformationAdapter
include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_win.cpp
)
if (NOT USE_QT_TABLET_WINDOWS)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
input/wintab/kis_tablet_support_win8.cpp
)
else()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
dialogs/KisDlgCustomTabletResolution.cpp
)
endif()
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
KisFrameDataSerializer.cpp
KisFrameCacheStore.cpp
KisFrameCacheSwapper.cpp
KisAbstractFrameCacheSwapper.cpp
KisInMemoryFrameCacheSwapper.cpp
input/wintab/drawpile_tablettester/tablettester.cpp
input/wintab/drawpile_tablettester/tablettest.cpp
)
if (UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
qtlockedfile/qtlockedfile_unix.cpp
)
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
widgets/KisDitherWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
forms/KisWelcomePage.ui
forms/WdgDlgPaletteEditor.ui
forms/KisNewsPage.ui
forms/wdgGamutMaskToolbar.ui
forms/wdgchangeclonesource.ui
brushhud/kis_dlg_brush_hud_config.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
layerstyles/wdgKisLayerStyleAngleSelector.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
input/wintab/drawpile_tablettester/tablettest.ui
)
if(WIN32)
if(USE_QT_TABLET_WINDOWS)
ki18n_wrap_ui(kritaui_LIB_SRCS
dialogs/KisDlgCustomTabletResolution.ui
)
else()
ki18n_wrap_ui(kritaui_LIB_SRCS
input/wintab/kis_screen_size_choice_dialog.ui
)
endif()
endif()
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$
$
$
$
$
$
$
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisCanvasWindow.cpp b/libs/ui/KisCanvasWindow.cpp
new file mode 100644
index 0000000000..ec33498252
--- /dev/null
+++ b/libs/ui/KisCanvasWindow.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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
+
+#include "KisCanvasWindow.h"
+#include "KisMainWindow.h"
+
+struct KisCanvasWindow::Private {
+ KisMainWindow *mainWindow;
+
+ Private(KisMainWindow *mainWindow)
+ : mainWindow(mainWindow)
+ {}
+};
+
+KisCanvasWindow::KisCanvasWindow(KisMainWindow *mainWindow)
+ : QWidget(mainWindow)
+ , d(new Private(mainWindow))
+{
+ setWindowFlags(Qt::Window);
+
+ QLayout *layout = new QHBoxLayout(this);
+ setLayout(layout);
+}
+
+KisCanvasWindow::~KisCanvasWindow() = default;
+
+void KisCanvasWindow::closeEvent(QCloseEvent *event)
+{
+ d->mainWindow->setCanvasDetached(false);
+ QWidget::closeEvent(event);
+}
+
+QWidget * KisCanvasWindow::swapMainWidget(QWidget *newWidget)
+{
+ QWidget *oldWidget = (layout()->count() > 0) ? (layout()->takeAt(0)->widget()) : nullptr;
+ if (newWidget) {
+ layout()->addWidget(newWidget);
+ }
+ return oldWidget;
+}
diff --git a/libs/widgets/tests/zoomcontroller_test.cpp b/libs/ui/KisCanvasWindow.h
similarity index 57%
copy from libs/widgets/tests/zoomcontroller_test.cpp
copy to libs/ui/KisCanvasWindow.h
index 921427db1a..36897f7be1 100644
--- a/libs/widgets/tests/zoomcontroller_test.cpp
+++ b/libs/ui/KisCanvasWindow.h
@@ -1,39 +1,42 @@
/*
- * Copyright (c) 2007-2010 Boudewijn Rempt
+ * 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.
*/
+#ifndef KISCANVASWINDOW_H
+#define KISCANVASWINDOW_H
-#include "zoomcontroller_test.h"
+#include
-#include
-#include
+class KisMainWindow;
-#include
-#include
-
-#include "KoCanvasControllerWidget.h"
-#include "KoZoomHandler.h"
-#include "KoZoomController.h"
-
-void zoomcontroller_test::testApi()
+/**
+ * Window for the canvas (mdi) area. Used when detached canvas mode is enabled.
+ */
+class KisCanvasWindow : public QWidget
{
- KoZoomHandler zoomHandler;
- KoZoomController zoomController(new KoCanvasControllerWidget(0), &zoomHandler, new KActionCollection(this));
- Q_UNUSED(zoomController);
+public:
+ explicit KisCanvasWindow(KisMainWindow *mainWindow);
+ ~KisCanvasWindow() override;
+
+ QWidget * swapMainWidget(QWidget *widget);
-}
+ void closeEvent(QCloseEvent *event) override;
+private:
+ struct Private;
+ QScopedPointer d;
+};
-QTEST_MAIN(zoomcontroller_test)
+#endif
diff --git a/libs/ui/KisDetailsPane.cpp b/libs/ui/KisDetailsPane.cpp
index 8b450772da..f4044837b5 100644
--- a/libs/ui/KisDetailsPane.cpp
+++ b/libs/ui/KisDetailsPane.cpp
@@ -1,113 +1,106 @@
/* This file is part of the KDE project
Copyright (C) 2005 Peter Simonsson
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 "KisDetailsPane.h"
#include
#include
////////////////////////////////////
// class KisDetailsPane
///////////////////////////////////
struct KisDetailsPanePrivate
{
QStandardItemModel m_model;
};
KisDetailsPane::KisDetailsPane(QWidget* parent, const QString& header)
: QWidget(parent),
Ui_KisDetailsPaneBase(),
d(new KisDetailsPanePrivate())
{
d->m_model.setHorizontalHeaderItem(0, new QStandardItem(header));
setupUi(this);
- m_previewLabel->installEventFilter(this);
m_documentList->installEventFilter(this);
m_documentList->setIconSize(QSize(IconExtent, IconExtent));
m_documentList->setModel(&d->m_model);
m_splitter->setSizes(QList() << 2 << 1);
changePalette();
connect(m_documentList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
this, SLOT(selectionChanged(QModelIndex)));
connect(m_documentList, SIGNAL(doubleClicked(QModelIndex)),
this, SLOT(openFile(QModelIndex)));
connect(m_openButton, SIGNAL(clicked()), this, SLOT(openFile()));
}
KisDetailsPane::~KisDetailsPane()
{
delete d;
}
bool KisDetailsPane::eventFilter(QObject* watched, QEvent* e)
{
- if (watched == m_previewLabel) {
- if (e->type() == QEvent::MouseButtonDblClick) {
- openFile();
- }
- }
-
if (watched == m_documentList) {
if ((e->type() == QEvent::Resize) && isVisible()) {
emit splitterResized(this, m_splitter->sizes());
}
if ((e->type() == QEvent::KeyPress)) {
QKeyEvent* keyEvent = static_cast(e);
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
openFile();
}
}
}
return false;
}
void KisDetailsPane::resizeSplitter(KisDetailsPane* sender, const QList& sizes)
{
if (sender == this)
return;
m_splitter->setSizes(sizes);
}
void KisDetailsPane::openFile()
{
QModelIndex index = m_documentList->selectionModel()->currentIndex();
openFile(index);
}
void KisDetailsPane::changePalette()
{
QPalette p = palette();
p.setBrush(QPalette::Base, QColor(Qt::transparent));
p.setColor(QPalette::Text, p.color(QPalette::Normal, QPalette::Foreground));
m_detailsLabel->setPalette(p);
}
QStandardItemModel* KisDetailsPane::model() const
{
return &d->m_model;
}
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 759c8eb128..5dac5bdee7 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2671 +1,2712 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2006 David Faure
Copyright (C) 2007, 2009 Thomas zander
Copyright (C) 2010 Benjamin Port
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 "KisMainWindow.h"
#include
// qt includes
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_selection_manager.h"
#include "kis_icon_utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoDockFactoryBase.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include
#include
#include
#include
#include
#include "KoToolDocker.h"
#include "KoToolBoxDocker_p.h"
#include
#include
#include
#include