diff --git a/krita/krita.action b/krita/krita.action
index 77d89a756e..e8febff460 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,2960 +1,3008 @@
General
Open Resources Folder
Opens a file browser at the location Krita saves resources such as brushes to.
Opens a file browser at the location Krita saves resources such as brushes to.
Open Resources Folder
0
0
false
Cleanup removed files...
Cleanup removed files
Cleanup removed files
0
0
false
C&ascade
Cascade
Cascade
10
0
false
&Tile
Tile
Tile
10
0
false
Create Resource Bundle...
Create Resource Bundle
Create Resource Bundle
0
0
false
Show File Toolbar
Show File Toolbar
Show File Toolbar
false
Show color selector
Show color selector
Show color selector
Shift+I
false
Show MyPaint shade selector
Show MyPaint shade selector
Show MyPaint shade selector
Shift+M
false
Show minimal shade selector
Show minimal shade selector
Show minimal shade selector
Shift+N
false
Show color history
Show color history
Show color history
H
false
Show common colors
Show common colors
Show common colors
U
false
Show Tool Options
Show Tool Options
Show Tool Options
\
false
Show Brush Editor
Show Brush Editor
Show Brush Editor
F5
false
Show Brush Presets
Show Brush Presets
Show Brush Presets
F6
false
Toggle Tablet Debugger
Toggle Tablet Debugger
Toggle Tablet Debugger
0
0
Ctrl+Shift+T
false
Rename Composition...
Rename Composition
Rename Composition
0
0
false
Update Composition
Update Composition
Update Composition
0
0
false
Painting
Make brush color lighter
Make brush color lighter
Make brush color lighter
0
0
L
false
Make brush color darker
Make brush color darker
Make brush color darker
0
0
K
false
Make brush color more saturated
Make brush color more saturated
Make brush color more saturated
false
Make brush color more desaturated
Make brush color more desaturated
Make brush color more desaturated
false
Shift brush color hue clockwise
Shift brush color hue clockwise
Shift brush color hue clockwise
false
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
false
Make brush color more red
Make brush color more red
Make brush color more red
false
Make brush color more green
Make brush color more green
Make brush color more green
false
Make brush color more blue
Make brush color more blue
Make brush color more blue
false
Make brush color more yellow
Make brush color more yellow
Make brush color more yellow
false
Increase opacity
Increase opacity
Increase opacity
0
0
O
false
Decrease opacity
Decrease opacity
Decrease opacity
0
0
I
false
draw-eraser
Set eraser mode
Set eraser mode
Set eraser mode
10000
0
E
true
view-refresh
Reload Original Preset
Reload Original Preset
Reload Original Preset
10000
false
transparency-unlocked
Preserve Alpha
Preserve Alpha
Preserve Alpha
10000
true
transform_icons_penPressure
Use Pen Pressure
Use Pen Pressure
Use Pen Pressure
10000
true
symmetry-horizontal
Horizontal Mirror Tool
Horizontal Mirror Tool
Horizontal Mirror Tool
10000
true
symmetry-vertical
Vertical Mirror Tool
Vertical Mirror Tool
Vertical Mirror Tool
10000
true
Paste at cursor
Paste at cursor
Paste at cursor
0
0
false
&Invert Selection
Invert current selection
Invert Selection
10000000000
100
Ctrl+Shift+I
false
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
10000
1
Ctrl+Shift+Backspace
false
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
10000
1
Ctrl+Backspace
false
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
10000
1
false
&Toggle Selection Display Mode
Toggle Selection Display Mode
Toggle Selection Display Mode
0
0
false
Next Favourite Preset
Next Favourite Preset
Next Favourite Preset
,
false
Previous Favourite Preset
Previous Favourite Preset
Previous Favourite Preset
.
false
preset-switcher
Switch to Previous Preset
Switch to Previous Preset
Switch to Previous Preset
/
false
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
true
Reset Foreground and Background Color
Reset Foreground and Background Color
Reset Foreground and Background Color
D
false
Swap Foreground and Background Color
Swap Foreground and Background Color
Swap Foreground and Background Color
X
false
smoothing-weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
false
smoothing-no
Brush Smoothing: Disabled
Brush Smoothing: Disabled
Brush Smoothing: Disabled
false
smoothing-stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
false
Decrease Brush Size
Decrease Brush Size
Decrease Brush Size
0
0
[
false
smoothing-basic
Brush Smoothing: Basic
Brush Smoothing: Basic
Brush Smoothing: Basic
false
Increase Brush Size
Increase Brush Size
Increase Brush Size
0
0
]
false
Toggle Assistant
Toggle Assistant
ToggleAssistant
Ctrl+Shift+L
true
Undo Polygon Selection Points
Undo Polygon Selection Points
Undo Polygon Selection Points
Shift+Z
false
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
10000
1
Ctrl+Shift+Backspace
false
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
10000
1
Ctrl+Backspace
false
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
10000
1
false
Convert &to Shape
Convert to Shape
Convert to Shape
10000000000
0
false
&Select Opaque
Select Opaque
Select Opaque
100000
100
false
&Show Global Selection Mask
Shows global selection as a usual selection mask in <interface>Layers</interface> docker
Show Global Selection Mask
100000
100
true
Filters
color-to-alpha
&Color to Alpha...
Color to Alpha
Color to Alpha
10000
0
false
&Top Edge Detection
Top Edge Detection
Top Edge Detection
10000
0
false
&Index Colors...
Index Colors
Index Colors
10000
0
false
Emboss Horizontal &Only
Emboss Horizontal Only
Emboss Horizontal Only
10000
0
false
D&odge
Dodge
Dodge
10000
0
false
&Sharpen
Sharpen
Sharpen
10000
0
false
B&urn
Burn
Burn
10000
0
false
&Mean Removal
Mean Removal
Mean Removal
10000
0
false
&Gaussian Blur...
Gaussian Blur
Gaussian Blur
10000
0
false
Emboss &in All Directions
Emboss in All Directions
Emboss in All Directions
10000
0
false
&Small Tiles...
Small Tiles
Small Tiles
10000
0
false
&Levels...
Levels
Levels
10000
0
Ctrl+L
false
&Sobel...
Sobel
Sobel
10000
0
false
&Wave...
Wave
Wave
10000
0
false
&Motion Blur...
Motion Blur
Motion Blur
10000
0
false
&Color Adjustment curves...
Color Adjustment curves
Color Adjustment curves
10000
0
Ctrl+M
false
Pi&xelize...
Pixelize
Pixelize
10000
0
false
Emboss (&Laplacian)
Emboss (Laplacian)
Emboss (Laplacian)
10000
0
false
&Left Edge Detection
Left Edge Detection
Left Edge Detection
10000
0
false
&Blur...
Blur
Blur
10000
0
false
&Raindrops...
Raindrops
Raindrops
10000
0
false
&Bottom Edge Detection
Bottom Edge Detection
Bottom Edge Detection
10000
0
false
&Random Noise...
Random Noise
Random Noise
10000
0
false
&Brightness/Contrast curve...
Brightness/Contrast curve
Brightness/Contrast curve
10000
0
false
Colo&r Balance..
Color Balance..
Color Balance..
10000
0
Ctrl+B
false
&PhongBumpmap...
PhongBumpmap
PhongBumpmap
10000
0
false
&Desaturate
Desaturate
Desaturate
10000
0
Ctrl+Shift+U
false
Color &Transfer...
Color Transfer
Color Transfer
10000
0
false
Emboss &Vertical Only
Emboss Vertical Only
Emboss Vertical Only
10000
0
false
&Lens Blur...
Lens Blur
Lens Blur
10000
0
false
M&inimize Channel
Minimize Channel
Minimize Channel
10000
0
false
M&aximize Channel
Maximize Channel
Maximize Channel
10000
0
false
&Oilpaint...
Oilpaint
Oilpaint
10000
0
false
&Right Edge Detection
Right Edge Detection
Right Edge Detection
10000
0
false
&Auto Contrast
Auto Contrast
Auto Contrast
10000
0
false
&Round Corners...
Round Corners
Round Corners
10000
0
false
&Unsharp Mask...
Unsharp Mask
Unsharp Mask
10000
0
false
&Emboss with Variable Depth...
Emboss with Variable Depth
Emboss with Variable Depth
10000
0
false
Emboss &Horizontal && Vertical
Emboss Horizontal & Vertical
Emboss Horizontal & Vertical
10000
0
false
Random &Pick...
Random Pick
Random Pick
10000
0
false
&Gaussian Noise Reduction...
Gaussian Noise Reduction
Gaussian Noise Reduction
10000
0
false
&Posterize...
Posterize
Posterize
10000
0
false
&Wavelet Noise Reducer...
Wavelet Noise Reducer
Wavelet Noise Reducer
10000
0
false
&HSV Adjustment...
HSV Adjustment
HSV Adjustment
10000
0
Ctrl+U
false
Tool Shortcuts
Dynamic Brush Tool
Dynamic Brush Tool
Dynamic Brush Tool
false
Crop Tool
Crop the image to an area
Crop the image to an area
C
false
Polygon Tool
Polygon Tool. Shift-mouseclick ends the polygon.
Polygon Tool. Shift-mouseclick ends the polygon.
false
References
References
References
false
Rectangle Tool
Rectangle Tool
Rectangle Tool
false
Multibrush Tool
Multibrush Tool
Multibrush Tool
Q
false
Shape Manipulation Tool
Shape Manipulation Tool
Shape Manipulation Tool
false
Color Picker
Select a color from the image or current layer
Select a color from the image or current layer
P
false
Text Editing Tool
Text editing
Text editing
false
Outline Selection Tool
Outline Selection Tool
Outline Selection Tool
false
Artistic Text Tool
Artistic text editing
Artistic text editing
false
Bezier Curve Selection Tool
Select a
Bezier Curve Selection Tool
false
Similar Color Selection Tool
Select a
Similar Color Selection Tool
false
Fill Tool
Fill a contiguous area of color with a color, or fill a selection.
Fill a contiguous area of color with a color, or fill a selection.
F
false
Line Tool
Line Tool
Line Tool
false
Freehand Path Tool
Freehand Path Tool
Freehand Path Tool
false
Bezier Curve Tool
Bezier Curve Tool. Shift-mouseclick ends the curve.
Bezier Curve Tool. Shift-mouseclick ends the curve.
false
Ellipse Tool
Ellipse Tool
Ellipse Tool
false
Freehand Brush Tool
Freehand Brush Tool
Freehand Brush Tool
B
false
Create object
Create object
Create object
false
Elliptical Selection Tool
Elliptical Selection Tool
Elliptical Selection Tool
J
false
Contiguous Selection Tool
Contiguous Selection Tool
Contiguous Selection Tool
false
Pattern editing
Pattern editing
Pattern editing
false
Review
Review
Review
false
Draw a gradient.
Draw a gradient.
Draw a gradient.
G
false
Polygonal Selection Tool
Polygonal Selection Tool
Polygonal Selection Tool
false
Measurement Tool
Measure the distance between two points
Measure the distance between two points
false
Rectangular Selection Tool
Rectangular Selection Tool
Rectangular Selection Tool
Ctrl+R
false
Move Tool
Move a layer
Move a layer
T
false
Vector Image Tool
Vector Image (EMF/WMF/SVM/SVG) tool
Vector Image (EMF/WMF/SVM/SVG) tool
false
Calligraphy
Calligraphy
Calligraphy
false
Path editing
Path editing
Path editing
false
Polyline Tool
Polyline Tool. Shift-mouseclick ends the polyline.
Polyline Tool. Shift-mouseclick ends the polyline.
false
Transform Tool
Transform a layer or a selection
Transform a layer or a selection
Ctrl+T
false
Ruler assistant editor tool
Ruler assistant editor tool
Ruler assistant editor tool
false
Text tool
Text tool
Text tool
false
Gradient Editing Tool
Gradient editing
Gradient editing
false
Blending Modes
Select Normal Blending Mode
Select Normal Blending Mode
Select Normal Blending Mode
0
0
Alt+Shift+N
false
Select Dissolve Blending Mode
Select Dissolve Blending Mode
Select Dissolve Blending Mode
0
0
Alt+Shift+I
false
Select Behind Blending Mode
Select Behind Blending Mode
Select Behind Blending Mode
0
0
Alt+Shift+Q
false
Select Clear Blending Mode
Select Clear Blending Mode
Select Clear Blending Mode
0
0
Alt+Shift+R
false
Select Darken Blending Mode
Select Darken Blending Mode
Select Darken Blending Mode
0
0
Alt+Shift+K
false
Select Multiply Blending Mode
Select Multiply Blending Mode
Select Multiply Blending Mode
0
0
Alt+Shift+M
false
Select Color Burn Blending Mode
Select Color Burn Blending Mode
Select Color Burn Blending Mode
0
0
Alt+Shift+B
false
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
0
0
Alt+Shift+A
false
Select Lighten Blending Mode
Select Lighten Blending Mode
Select Lighten Blending Mode
0
0
Alt+Shift+G
false
Select Screen Blending Mode
Select Screen Blending Mode
Select Screen Blending Mode
0
0
Alt+Shift+S
false
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
0
0
Alt+Shift+D
false
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
0
0
Alt+Shift+W
false
Select Overlay Blending Mode
Select Overlay Blending Mode
Select Overlay Blending Mode
0
0
Alt+Shift+O
false
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
0
0
Alt+Shift+P
false
Select Soft Light Blending Mode
Select Soft Light Blending Mode
Select Soft Light Blending Mode
0
0
Alt+Shift+F
false
Select Hard Light Blending Mode
Select Hard Light Blending Mode
Select Hard Light Blending Mode
0
0
Alt+Shift+H
false
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
0
0
Alt+Shift+V
false
Select Linear Light Blending Mode
Select Linear Light Blending Mode
Select Linear Light Blending Mode
0
0
Alt+Shift+J
false
Select Pin Light Blending Mode
Select Pin Light Blending Mode
Select Pin Light Blending Mode
0
0
Alt+Shift+Z
false
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
0
0
Alt+Shift+L
false
Select Difference Blending Mode
Select Difference Blending Mode
Select Difference Blending Mode
0
0
Alt+Shift+E
false
Select Exclusion Blending Mode
Select Exclusion Blending Mode
Select Exclusion Blending Mode
0
0
Alt+Shift+X
false
Select Hue Blending Mode
Select Hue Blending Mode
Select Hue Blending Mode
0
0
Alt+Shift+U
false
Select Saturation Blending Mode
Select Saturation Blending Mode
Select Saturation Blending Mode
0
0
Alt+Shift+T
false
Select Color Blending Mode
Select Color Blending Mode
Select Color Blending Mode
0
0
Alt+Shift+C
false
Select Luminosity Blending Mode
Select Luminosity Blending Mode
Select Luminosity Blending Mode
0
0
Alt+Shift+Y
false
Animation
Previous frame
Move to previous frame
Move to previous frame
1
0
false
Next frame
Move to next frame
Move to next frame
1
0
false
Play / pause animation
Play / pause animation
Play / pause animation
1
0
false
Add blank frame
Add blank frame
Add blank frame
100000
0
false
Copy Frame
Add duplicate frame
Add duplicate frame
100000
0
false
Toggle onion skin
Toggle onion skin
Toggle onion skin
100000
0
false
Previous Keyframe
false
Next Keyframe
false
First Frame
false
Last Frame
false
Auto Frame Mode
true
true
Add blank frame
Add blank frame
Add blank frame
100000
0
false
Show in Timeline
true
Layers
Activate next layer
Activate next layer
Activate next layer
1000
0
PgUp
false
Activate previous layer
Activate previous layer
Activate previous layer
1000
0
PgDown
false
Activate previously selected layer
Activate previously selected layer
Activate previously selected layer
1000
0
;
false
groupLayer
&Group Layer
Group Layer
Group Layer
1000
0
false
cloneLayer
&Clone Layer
Clone Layer
Clone Layer
1000
0
false
vectorLayer
&Vector Layer
Vector Layer
Vector Layer
1000
0
false
filterLayer
&Filter Layer...
Filter Layer
Filter Layer
1000
0
false
fillLayer
&Fill Layer...
Fill Layer
Fill Layer
1000
0
false
fileLayer
&File Layer...
File Layer
File Layer
1000
0
false
transparencyMask
&Transparency Mask
Transparency Mask
Transparency Mask
100000
0
false
filterMask
&Filter Mask...
Filter Mask
Filter Mask
100000
0
false
filterMask
&Colorize Mask
Colorize Mask
Colorize Mask
100000
0
false
transformMask
&Transform Mask...
Transform Mask
Transform Mask
100000
0
false
selectionMask
&Local Selection
Local Selection
Local Selection
100000
0
false
view-filter
&Isolate Layer
Isolate Layer
Isolate Layer
1000
0
true
+
+ layer-locked
+ &Lock/unlock layer
+
+ Lock/unlock layer
+ Lock/unlock layer
+ 1000
+ 0
+
+ false
+
+
+
+ visible
+ Toggle layer &visibility
+
+ Toggle layer visibility
+ Toggle layer visibility
+ 1000
+ 0
+
+ false
+
+
+
+ transparency-locked
+ Lock/unlock layer &alpha
+
+ Lock/unlock layer's alpha
+ Lock/unlock layer's alpha
+ 1000
+ 0
+
+ false
+
+
+
+ transparency-enabled
+ Toggle layer alpha &inheritance
+
+ Toggle layer alpha inheritance
+ Toggle layer alpha inheritance
+ 1000
+ 0
+
+ false
+
+
paintLayer
&Paint Layer
Paint Layer
Paint Layer
1000
0
Insert
false
&New Layer From Visible
New layer from visible
New layer from visible
1000
0
false
duplicatelayer
&Duplicate Layer or Mask
Duplicate Layer or Mask
Duplicate Layer or Mask
1000
0
Ctrl+J
false
&Cut Selection to New Layer
Cut Selection to New Layer
Cut Selection to New Layer
100000000
1
Ctrl+Shift+J
false
Copy &Selection to New Layer
Copy Selection to New Layer
Copy Selection to New Layer
100000000
0
Ctrl+Alt+J
false
Copy Layer
Copy layer to clipboard
Copy layer to clipboard
10000
0
false
Cut Layer
Cut layer to clipboard
Cut layer to clipboard
10000
0
false
Paste Layer
Paste layer from clipboard
Paste layer from clipboard
10000
0
false
Quick Group
Create a group layer containing selected layers
Quick Group
100000
0
Ctrl+G
false
Quick Ungroup
Remove grouping of the layers or remove one layer out of the group
Quick Ungroup
100000
0
Ctrl+Alt+G
false
Quick Clipping Group
Group selected layers and add a layer with clipped alpha channel
Quick Clipping Group
100000
0
Ctrl+Shift+G
false
All Layers
Select all layers
Select all layers
10000
0
false
Visible Layers
Select all visible layers
Select all visible layers
10000
0
false
Locked Layers
Select all locked layers
Select all locked layers
10000
0
false
Invisible Layers
Select all invisible layers
Select all invisible layers
10000
0
false
Unlocked Layers
Select all unlocked layers
Select all unlocked layers
10000
0
false
document-save
&Save Layer/Mask...
Save Layer/Mask
Save Layer/Mask
1000
0
false
document-save
Save &Group Layers...
Save Group Layers
Save Group Layers
100000
0
false
Convert group to &animated layer
Convert child layers into animation frames
Convert child layers into animation frames
100000
0
false
I&mport Layer...
Import Layer
Import Layer
100000
0
false
paintLayer
&as Paint Layer...
as Paint Layer
as Paint Layer
1000
0
false
transparencyMask
as &Transparency Mask...
as Transparency Mask
as Transparency Mask
1000
0
false
filterMask
as &Filter Mask...
as Filter Mask
as Filter Mask
1000
0
false
selectionMask
as &Selection Mask...
as Selection Mask
as Selection Mask
1000
0
false
paintLayer
to &Paint Layer
to Paint Layer
to Paint Layer
1000
0
false
transparencyMask
to &Transparency Mask
to Transparency Mask
to Transparency Mask
1000
0
false
filterMask
to &Filter Mask...
to Filter Mask
to Filter Mask
1000
0
false
selectionMask
to &Selection Mask
to Selection Mask
to Selection Mask
1000
0
false
transparencyMask
&Alpha into Mask
Alpha into Mask
Alpha into Mask
100000
10
false
transparency-enabled
&Write as Alpha
Write as Alpha
Write as Alpha
1000000
1
false
document-save
&Save Merged...
Save Merged
Save Merged
1000000
0
false
split-layer
Split Layer...
Split Layer
Split Layer
1000
0
false
Wavelet Decompose ...
Wavelet Decompose
Wavelet Decompose
1000
1
false
symmetry-horizontal
Mirror Layer Hori&zontally
Mirror Layer Horizontally
Mirror Layer Horizontally
1000
1
false
symmetry-vertical
Mirror Layer &Vertically
Mirror Layer Vertically
Mirror Layer Vertically
1000
1
false
&Rotate Layer...
Rotate Layer
Rotate Layer
100000
1
false
object-rotate-right
Rotate &Layer 90° to the Right
Rotate Layer 90° to the Right
Rotate Layer 90° to the Right
100000
1
false
object-rotate-left
Rotate Layer &90° to the Left
Rotate Layer 90° to the Left
Rotate Layer 90° to the Left
100000
1
false
Rotate Layer &180°
Rotate Layer 180°
Rotate Layer 180°
100000
1
false
Scale &Layer to new Size...
Scale Layer to new Size
Scale Layer to new Size
100000
1
false
&Shear Layer...
Shear Layer
Shear Layer
100000
1
false
&Offset Layer...
Offset Layer
Offset Layer
100000
1
false
Clones &Array...
Clones Array
Clones Array
100000
0
false
&Edit metadata...
Edit metadata
Edit metadata
100000
1
false
&Histogram...
Histogram
Histogram
100000
0
false
&Convert Layer Color Space...
Convert Layer Color Space
Convert Layer Color Space
100000
1
false
merge-layer-below
&Merge with Layer Below
Merge with Layer Below
Merge with Layer Below
100000
0
Ctrl+E
false
&Flatten Layer
Flatten Layer
Flatten Layer
100000
0
false
Ras&terize Layer
Rasterize Layer
Rasterize Layer
10000000
1
false
Flatten ima&ge
Flatten image
Flatten image
100000
0
Ctrl+Shift+E
false
La&yer Style...
Layer Style
Layer Style
100000
1
false
Move into previous group
Move into previous group
Move into previous group
0
0
false
Move into next group
Move into next group
Move into next group
0
0
false
Rename current layer
Rename current layer
Rename current layer
100000
0
F2
false
deletelayer
&Remove Layer
Remove Layer
Remove Layer
1000
1
Shift+Delete
false
arrowupblr
Move Layer or Mask Up
Move Layer or Mask Up
Ctrl+PgUp
false
arrowdown
Move Layer or Mask Down
Move Layer or Mask Down
Ctrl+PgDown
false
properties
&Properties...
Properties
Properties
1000
1
F3
false
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index e861a0aab5..374e5a1cb8 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1335 +1,1415 @@
/*
* 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_node_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
#include "kis_config.h"
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
{
}
KisNodeManager * q;
KisViewManager * view;
QPointerimageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer nodeSelectionAdapter;
QScopedPointer nodeInsertionAdapter;
KisNodeList selectedNodes;
QPointer nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast(shape);
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = dynamic_cast(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointerimageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
if (!KisConfig().disableColorizeMaskFeature()) {
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
}
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(createNode(const QString &)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(convertNode(const QString &)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
+ action = actionManager->createAction("toggle_layer_lock");
+ connect(action, SIGNAL(triggered()), this, SLOT(toggleLock()));
+
+ action = actionManager->createAction("toggle_layer_visibility");
+ connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility()));
+
+ action = actionManager->createAction("toggle_layer_alpha_lock");
+ connect(action, SIGNAL(triggered()), this, SLOT(toggleAlphaLock()));
+
+ action = actionManager->createAction("toggle_layer_inherit_alpha");
+ connect(action, SIGNAL(triggered()), this, SLOT(toggleInheritAlpha()));
+
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast(node.data());
KisLayer *l = dynamic_cast(parent.data());
KisSelectionMaskSP selMask = l->selectionMask();
if (m && m->active() && l && l->selectionMask())
selMask->setActive(false);
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
if (checked) {
KisNodeSP activeNode = this->activeNode();
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryFinishIsolatedMode()
{
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode) return;
this->toggleIsolateMode(true);
}
void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
if (!m_d->view->blockUntillOperationsFinished(m_d->view->image())) {
return;
}
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN(activeNode);
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
m_d->layerManager.addLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
m_d->layerManager.addCloneLayer(activeNode);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.addFileLayer(activeNode);
}
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
return m_d->layerManager.addLayer(activeNode);
}
void KisNodeManager::convertNode(const QString &nodeType)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, true);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true);
}
m_d->commandsAdapter.removeNode(activeNode);
m_d->commandsAdapter.endMacro();
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
if (node->inherits("KisShapeLayer")) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if (selectedNodes().size() > 1 || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
} else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity,
bool finalChange)
{
if (!node) return;
if (node->opacity() == opacity) return;
if (!finalChange) {
node->setOpacity(opacity);
node->setDirty();
} else {
m_d->commandsAdapter.setOpacity(node, opacity);
}
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity), finalChange);
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
!nodeJuggler->canMergeAction(actionName))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal);
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical);
}
inline bool checkForGlobalSelection(KisNodeSP node) {
return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent();
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::mergeLayer()
{
m_d->layerManager.mergeLayer();
}
void KisNodeManager::rotate(double radians)
{
// XXX: implement rotation for masks as well
m_d->layerManager.rotateLayer(radians);
}
void KisNodeManager::rotate180()
{
rotate(M_PI);
}
void KisNodeManager::rotateLeft90()
{
rotate(-M_PI / 2);
}
void KisNodeManager::rotateRight90()
{
rotate(M_PI / 2);
}
void KisNodeManager::shear(double angleX, double angleY)
{
// XXX: implement shear for masks as well
m_d->layerManager.shearLayer(angleX, angleY);
}
void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy)
{
KisNodeSP node = activeNode();
KIS_ASSERT_RECOVER_RETURN(node);
m_d->view->image()->scaleNode(node, sx, sy, filterStrategy);
nodesUpdated();
}
void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);;
QScopedPointer d(KisPart::instance()->createDocument());
d->prepareForImport();
KisImageSP dst = new KisImage(d->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
d->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
d->setOutputMimeType(mimefilter.toLatin1());
d->exportDocument(url);
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
m_d->saveDeviceAsImage(device, node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
do {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.").arg(parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
do {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
+void KisNodeManager::toggleLock()
+{
+ KisNodeList nodes = this->selectedNodes();
+ KisNodeSP active = activeNode();
+ if (nodes.isEmpty() || !active) return;
+
+ bool isLocked = active->userLocked();
+
+ for (auto &node : nodes) {
+ node->setUserLocked(!isLocked);
+ }
+}
+
+void KisNodeManager::toggleVisibility()
+{
+ KisNodeList nodes = this->selectedNodes();
+ KisNodeSP active = activeNode();
+ if (nodes.isEmpty() || !active) return;
+
+ bool isVisible = active->visible();
+
+ for (auto &node : nodes) {
+ node->setVisible(!isVisible);
+ node->setDirty();
+ }
+}
+
+void KisNodeManager::toggleAlphaLock()
+{
+ KisNodeList nodes = this->selectedNodes();
+ KisNodeSP active = activeNode();
+ if (nodes.isEmpty() || !active) return;
+
+ auto layer = qobject_cast(active.data());
+ if (!layer) {
+ return;
+ }
+
+ bool isAlphaLocked = layer->alphaLocked();
+ for (auto &node : nodes) {
+ auto layer = qobject_cast(node.data());
+ if (layer) {
+ layer->setAlphaLocked(!isAlphaLocked);
+ }
+ }
+}
+
+void KisNodeManager::toggleInheritAlpha()
+{
+ KisNodeList nodes = this->selectedNodes();
+ KisNodeSP active = activeNode();
+ if (nodes.isEmpty() || !active) return;
+
+ auto layer = qobject_cast(active.data());
+ if (!layer) {
+ return;
+ }
+
+ bool isAlphaDisabled = layer->alphaChannelDisabled();
+ for (auto &node : nodes) {
+ auto layer = qobject_cast(node.data());
+ if (layer) {
+ layer->disableAlphaChannel(!isAlphaDisabled);
+ node->setDirty();
+ }
+ }
+}
+
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisClipboard::instance()->setLayers(nodes, m_d->view->image(), true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast(active.data())) {
KisNodeList nodes = active->childNodes(QStringList(), KoProperties());
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, active);
juggler->removeNode(KisNodeList() << active);
} else if (parent && parent->parent()) {
KisNodeSP grandParent = parent->parent();
KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties());
KisNodeList allSelectedNodes = selectedNodes();
const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes);
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(allSelectedNodes, grandParent, parent);
if (removeParent) {
juggler->removeNode(KisNodeList() << parent);
}
}
}
void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps)
{
KisImageSP image = m_d->view->image();
KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true);
KisNodeList selectedNodes = this->selectedNodes();
if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) {
nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true);
}
if (!nodes.isEmpty()) {
slotImageRequestNodeReselection(nodes.last(), nodes);
}
}
void KisNodeManager::selectAllNodes()
{
KoProperties props;
selectLayersImpl(props, props);
}
void KisNodeManager::selectVisibleNodes()
{
KoProperties props;
props.setProperty("visible", true);
KoProperties invertedProps;
invertedProps.setProperty("visible", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectLockedNodes()
{
KoProperties props;
props.setProperty("locked", true);
KoProperties invertedProps;
invertedProps.setProperty("locked", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectInvisibleNodes()
{
KoProperties props;
props.setProperty("visible", false);
KoProperties invertedProps;
invertedProps.setProperty("visible", true);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectUnlockedNodes()
{
KoProperties props;
props.setProperty("locked", false);
KoProperties invertedProps;
invertedProps.setProperty("locked", true);
selectLayersImpl(props, invertedProps);
}
diff --git a/libs/ui/kis_node_manager.h b/libs/ui/kis_node_manager.h
index 58cc900664..9455adb57e 100644
--- a/libs/ui/kis_node_manager.h
+++ b/libs/ui/kis_node_manager.h
@@ -1,256 +1,261 @@
/*
* 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_NODE_MANAGER
#define KIS_NODE_MANAGER
#include
#include
#include "kis_types.h"
#include
class KActionCollection;
class KoCompositeOp;
class KoColorSpace;
class KUndo2MagicString;
class KisFilterStrategy;
class KisViewManager;
class KisActionManager;
class KisView;
class KisNodeSelectionAdapter;
class KisNodeInsertionAdapter;
class KisNodeJugglerCompressed;
class KoProperties;
/**
* The node manager passes requests for new layers or masks on to the mask and layer
* managers.
*/
class KRITAUI_EXPORT KisNodeManager : public QObject
{
Q_OBJECT
public:
KisNodeManager(KisViewManager * view);
~KisNodeManager() override;
void setView(QPointerimageView);
Q_SIGNALS:
/// emitted whenever a node is selected.
void sigNodeActivated(KisNodeSP node);
/// emitted whenever a different layer is selected.
void sigLayerActivated(KisLayerSP layer);
/// for the layer box: this sets the current node in the layerbox
/// without telling the node manager that the node is activated,
/// preventing loops (I think...)
void sigUiNeedChangeActiveNode(KisNodeSP node);
void sigUiNeedChangeSelectedNodes(const KisNodeList &nodes);
public:
void setup(KActionCollection * collection, KisActionManager* actionManager);
void updateGUI();
/// Convenience function to get the active layer or mask
KisNodeSP activeNode();
/// convenience function to get the active layer. If a mask is
/// active, it's parent layer is the active layer.
KisLayerSP activeLayer();
/// Get the paint device the user wants to paint on now
KisPaintDeviceSP activePaintDevice();
/**
* @return the active color space used for composition, meaning the color space
* of the active mask, or the color space of the parent of the active layer
*/
const KoColorSpace* activeColorSpace();
/**
* Sets opacity for the node in a universal way (masks/layers)
*/
void setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange);
/**
* Sets compositeOp for the node in a universal way (masks/layers)
*/
void setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp);
KisNodeList selectedNodes();
KisNodeSelectionAdapter* nodeSelectionAdapter() const;
KisNodeInsertionAdapter* nodeInsertionAdapter() const;
public Q_SLOTS:
/**
* Explicitly activates \p node
* The UI will be noticed that active node has been changed.
* Both sigNodeActivated and sigUiNeedChangeActiveNode are emitted.
*
* WARNING: normally you needn't call this method manually. It is
* automatically called when a node is added to the graph. If you
* have some special cases when you need to activate a node, consider
* adding them to KisDummiesFacadeBase instead. Calling this method
* directly should be the last resort.
*
* \see slotUiActivatedNode for comparison
*/
void slotNonUiActivatedNode(KisNodeSP node);
/**
* Activates \p node.
* All non-ui listeners are notified with sigNodeActivated,
* sigUiNeedChangeActiveNode is *not* emitted.
*
* \see activateNode
*/
void slotUiActivatedNode(KisNodeSP node);
/**
* Adds a list of nodes without searching appropriate position for
* it. You *must* ensure that the nodes are allowed to be added
* to the parent, otherwise you'll get an assert.
*/
void addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Moves a list of nodes without searching appropriate position
* for it. You *must* ensure that the nodes are allowed to be
* added to the parent, otherwise you'll get an assert.
*/
void moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Copies a list of nodes without searching appropriate position
* for it. You *must* ensure that the nodes are allowed to be
* added to the parent, otherwise you'll get an assert.
*/
void copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis);
/**
* Create new layer from actually visible
*/
void createFromVisible();
void toggleIsolateActiveNode();
void toggleIsolateMode(bool checked);
void slotUpdateIsolateModeAction();
void slotTryFinishIsolatedMode();
void moveNodeAt(KisNodeSP node, KisNodeSP parent, int index);
void createNode(const QString& nodeType, bool quiet = false, KisPaintDeviceSP copyFrom = 0);
void convertNode(const QString &nodeType);
void nodesUpdated();
void nodeProperties(KisNodeSP node);
void nodeOpacityChanged(qreal opacity, bool finalChange);
void nodeCompositeOpChanged(const KoCompositeOp* op);
void duplicateActiveNode();
void removeNode();
void mirrorNodeX();
void mirrorNodeY();
void mirrorNode(KisNodeSP node, const KUndo2MagicString& commandName, Qt::Orientation orientation);
void activateNextNode();
void activatePreviousNode();
void switchToPreviouslyActiveNode();
/**
* move the active node up the nodestack.
*/
void raiseNode();
/**
* move the active node down the nodestack
*/
void lowerNode();
void rotate(double radians);
void rotate180();
void rotateLeft90();
void rotateRight90();
void saveNodeAsImage();
// merges the active layer with the layer below it.
void mergeLayer();
void slotSplitAlphaIntoMask();
void slotSplitAlphaWrite();
void slotSplitAlphaSaveMerged();
+ void toggleLock();
+ void toggleVisibility();
+ void toggleAlphaLock();
+ void toggleInheritAlpha();
+
/**
* @brief slotSetSelectedNodes set the list of nodes selected in the layerbox. Selected nodes are not necessarily active nodes.
* @param nodes the selected nodes
*/
void slotSetSelectedNodes(const KisNodeList &nodes);
void slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
void cutLayersToClipboard();
void copyLayersToClipboard();
void pasteLayersFromClipboard();
void createQuickGroup();
void createQuickClippingGroup();
void quickUngroup();
void selectAllNodes();
void selectVisibleNodes();
void selectLockedNodes();
void selectInvisibleNodes();
void selectUnlockedNodes();
public:
void shear(double angleX, double angleY);
void scale(double sx, double sy, KisFilterStrategy *filterStrategy);
void removeSingleNode(KisNodeSP node);
KisLayerSP createPaintLayer();
private:
/**
* Scales opacity from the range 0...1
* to the integer range 0...255
*/
qint32 convertOpacityToInt(qreal opacity);
void removeSelectedNodes(KisNodeList selectedNodes);
void slotSomethingActivatedNodeImpl(KisNodeSP node);
void createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild);
void selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps);
struct Private;
Private * const m_d;
};
#endif
diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp
index 8417de1757..ad7ef0e881 100644
--- a/plugins/dockers/defaultdockers/kis_layer_box.cpp
+++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp
@@ -1,938 +1,943 @@
/*
* kis_layer_box.cc - part of Krita aka Krayon aka KimageShop
*
* Copyright (c) 2002 Patrick Julien
* Copyright (C) 2006 Gábor Lehel
* Copyright (C) 2007 Thomas Zander
* Copyright (C) 2007 Boudewijn Rempt
* Copyright (c) 2011 José Luis Vergara
*
* 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_layer_box.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_action.h"
#include "kis_action_manager.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_slider_spin_box.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_node_model.h"
#include "canvas/kis_canvas2.h"
#include "KisDocument.h"
#include "kis_dummies_facade_base.h"
#include "kis_shape_controller.h"
#include "kis_selection_mask.h"
#include "kis_config.h"
#include "KisView.h"
#include "krita_utils.h"
#include "sync_button_and_action.h"
#include "kis_color_label_selector_widget.h"
#include "kis_signals_blocker.h"
#include "kis_color_filter_combo.h"
#include "kis_node_filter_proxy_model.h"
#include "kis_layer_utils.h"
#include "ui_wdglayerbox.h"
inline void KisLayerBox::connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id)
{
if (!view || !button) return;
KisAction *action = view->actionManager()->actionByName(id);
if (!action) return;
connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool)));
}
inline void KisLayerBox::addActionToMenu(QMenu *menu, const QString &id)
{
if (m_canvas) {
menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id));
}
}
KisLayerBox::KisLayerBox()
: QDockWidget(i18n("Layers"))
, m_canvas(0)
, m_wdgLayerBox(new Ui_WdgLayerBox)
, m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE)
{
KisConfig cfg;
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_opacityDelayTimer.setSingleShot(true);
m_wdgLayerBox->setupUi(mainWidget);
connect(m_wdgLayerBox->listLayers,
SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)),
this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(collapsed(const QModelIndex&)), SLOT(slotCollapsed(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(expanded(const QModelIndex&)), SLOT(slotExpanded(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(selectionChanged(const QModelIndexList&)), SLOT(selectionChanged(const QModelIndexList&)));
m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnRaise->setEnabled(false);
m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr"));
m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setEnabled(false);
m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown"));
m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties"));
m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22));
if (cfg.sliderLabels()) {
m_wdgLayerBox->opacityLabel->hide();
m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity")));
}
m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0);
m_wdgLayerBox->doubleOpacity->setSuffix("%");
connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal)));
connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged()));
connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int)));
m_selectOpaque = new KisAction(i18n("&Select Opaque"), this);
m_selectOpaque->setActivationFlags(KisAction::ACTIVE_LAYER);
m_selectOpaque->setActivationConditions(KisAction::SELECTION_EDITABLE);
m_selectOpaque->setObjectName("select_opaque");
connect(m_selectOpaque, SIGNAL(triggered(bool)), this, SLOT(slotSelectOpaque()));
m_actions.append(m_selectOpaque);
m_newLayerMenu = new QMenu(this);
m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu);
m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup);
m_nodeModel = new KisNodeModel(this);
m_filteringModel = new KisNodeFilterProxyModel(this);
m_filteringModel->setNodeModel(m_nodeModel);
/**
* Connect model updateUI() to enable/disable controls.
* Note: nodeActivated() is connected separately in setImage(), because
* it needs particular order of calls: first the connection to the
* node manager should be called, then updateUI()
*/
connect(m_nodeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset()));
KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this);
showGlobalSelectionMask->setObjectName("show-global-selection-mask");
showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE);
showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker"));
showGlobalSelectionMask->setCheckable(true);
connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool)));
m_actions.append(showGlobalSelectionMask);
showGlobalSelectionMask->setChecked(cfg.showGlobalSelection());
m_colorSelector = new KisColorLabelSelectorWidget(this);
connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int)));
m_colorSelectorAction = new QWidgetAction(this);
m_colorSelectorAction->setDefaultWidget(m_colorSelector);
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
&m_colorLabelCompressor, SLOT(start()));
m_wdgLayerBox->listLayers->setModel(m_filteringModel);
// this connection should be done *after* the setModel() call to
// happen later than the internal selection model
connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved,
this, &KisLayerBox::slotAboutToRemoveRows);
connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering()));
setEnabled(false);
connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels()));
}
KisLayerBox::~KisLayerBox()
{
delete m_wdgLayerBox;
}
void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, KisNodeView *nodeView)
{
if (!root) return;
if (filteringModel.isNull()) return;
if (!nodeView) return;
nodeView->blockSignals(true);
KisNodeSP node = root->firstChild();
while (node) {
QModelIndex idx = filteringModel->indexFromNode(node);
if (idx.isValid()) {
nodeView->setExpanded(idx, !node->collapsed());
}
if (node->childCount() > 0) {
expandNodesRecursively(node, filteringModel, nodeView);
}
node = node->nextSibling();
}
nodeView->blockSignals(false);
}
void KisLayerBox::setMainWindow(KisViewManager* kisview)
{
m_nodeManager = kisview->nodeManager();
Q_FOREACH (KisAction *action, m_actions) {
kisview->actionManager()->
addAction(action->objectName(),
action);
}
connectActionToButton(kisview, m_wdgLayerBox->bnAdd, "add_new_paint_layer");
connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer");
KisActionManager *actionManager = kisview->actionManager();
KisAction *action = actionManager->createAction("RenameCurrentLayer");
Q_ASSERT(action);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode()));
m_propertiesAction = actionManager->createAction("layer_properties");
Q_ASSERT(m_propertiesAction);
new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this);
connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked()));
m_removeAction = actionManager->createAction("remove_layer");
Q_ASSERT(m_removeAction);
new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this);
connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked()));
action = actionManager->createAction("move_layer_up");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked()));
action = actionManager->createAction("move_layer_down");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked()));
}
void KisLayerBox::setCanvas(KoCanvasBase *canvas)
{
if(m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
animation->disconnect(this);
}
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
}
m_canvas = dynamic_cast(canvas);
if (m_canvas) {
m_image = m_canvas->image();
connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start()));
KisDocument* doc = static_cast(m_canvas->imageView()->document());
KisShapeController *kritaShapeController =
dynamic_cast(doc->shapeController());
KisDummiesFacadeBase *kritaDummiesFacade =
static_cast(kritaShapeController);
m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_nodeManager->nodeSelectionAdapter(), m_nodeManager->nodeInsertionAdapter());
connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged()));
// cold start
if (m_nodeManager) {
setCurrentNode(m_nodeManager->activeNode());
// Connection KisNodeManager -> KisLayerBox
connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
this, SLOT(setCurrentNode(KisNodeSP)));
connect(m_nodeManager,
SIGNAL(sigUiNeedChangeSelectedNodes(const QList &)),
SLOT(slotNodeManagerChangedSelection(const QList &)));
}
else {
setCurrentNode(m_canvas->imageView()->currentNode());
}
// Connection KisLayerBox -> KisNodeManager (isolate layer)
connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
m_nodeManager, SLOT(toggleIsolateActiveNode()));
KisImageAnimationInterface *animation = m_image->animationInterface();
connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &KisLayerBox::slotImageTimeChanged);
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex());
updateAvailableLabels();
addActionToMenu(m_newLayerMenu, "add_new_paint_layer");
addActionToMenu(m_newLayerMenu, "add_new_group_layer");
addActionToMenu(m_newLayerMenu, "add_new_clone_layer");
addActionToMenu(m_newLayerMenu, "add_new_shape_layer");
addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer");
addActionToMenu(m_newLayerMenu, "add_new_fill_layer");
addActionToMenu(m_newLayerMenu, "add_new_file_layer");
m_newLayerMenu->addSeparator();
addActionToMenu(m_newLayerMenu, "add_new_transparency_mask");
addActionToMenu(m_newLayerMenu, "add_new_filter_mask");
if (!KisConfig().disableColorizeMaskFeature()) {
addActionToMenu(m_newLayerMenu, "add_new_colorize_mask");
}
addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
}
}
void KisLayerBox::unsetCanvas()
{
setEnabled(false);
if (m_canvas) {
m_newLayerMenu->clear();
}
m_filteringModel->unsetDummiesFacade();
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
m_canvas = 0;
}
void KisLayerBox::notifyImageDeleted()
{
setCanvas(0);
}
void KisLayerBox::updateUI()
{
if (!m_canvas) return;
if (!m_nodeManager) return;
KisNodeSP activeNode = m_nodeManager->activeNode();
if (activeNode != m_activeNode) {
if( !m_activeNode.isNull() )
m_activeNode->disconnect(this);
m_activeNode = activeNode;
if (activeNode) {
KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false);
if (opacityChannel) {
watchOpacityChannel(opacityChannel);
} else {
watchOpacityChannel(0);
connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &KisLayerBox::slotKeyframeChannelAdded);
}
}
}
m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false));
m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false));
if (activeNode) {
if (m_nodeManager->activePaintDevice()) {
slotFillCompositeOps(m_nodeManager->activeColorSpace());
} else {
slotFillCompositeOps(m_image->colorSpace());
}
if (activeNode->inherits("KisColorizeMask") ||
activeNode->inherits("KisLayer")) {
m_wdgLayerBox->doubleOpacity->setEnabled(true);
slotSetOpacity(activeNode->opacity() * 100.0 / 255);
const KoCompositeOp* compositeOp = activeNode->compositeOp();
if (compositeOp) {
slotSetCompositeOp(compositeOp);
} else {
m_wdgLayerBox->cmbComposite->setEnabled(false);
}
const KisGroupLayer *group = qobject_cast(activeNode.data());
bool compositeSelectionActive = !(group && group->passThroughMode());
m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive);
} else if (activeNode->inherits("KisMask")) {
m_wdgLayerBox->cmbComposite->setEnabled(false);
m_wdgLayerBox->doubleOpacity->setEnabled(false);
}
}
}
/**
* This method is callen *only* when non-GUI code requested the
* change of the current node
*/
void KisLayerBox::setCurrentNode(KisNodeSP node)
{
m_filteringModel->setActiveNode(node);
QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex();
m_filteringModel->setData(index, true, KisNodeModel::ActiveRole);
updateUI();
}
void KisLayerBox::slotModelReset()
{
if(m_nodeModel->hasDummiesFacade()) {
QItemSelection selection;
Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) {
const QModelIndex &idx = m_filteringModel->indexFromNode(node);
if(idx.isValid()){
QItemSelectionRange selectionRange(idx);
selection << selectionRange;
}
}
m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
}
updateUI();
}
void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp)
{
KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
m_wdgLayerBox->cmbComposite->blockSignals(true);
m_wdgLayerBox->cmbComposite->selectCompositeOp(opId);
m_wdgLayerBox->cmbComposite->blockSignals(false);
}
void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace)
{
m_wdgLayerBox->cmbComposite->validate(colorSpace);
}
// range: 0-100
void KisLayerBox::slotSetOpacity(double opacity)
{
Q_ASSERT(opacity >= 0 && opacity <= 100);
m_wdgLayerBox->doubleOpacity->blockSignals(true);
m_wdgLayerBox->doubleOpacity->setValue(opacity);
m_wdgLayerBox->doubleOpacity->blockSignals(false);
}
void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
KisNodeSP activeNode = m_nodeManager->activeNode();
if (nodes.isEmpty() || !activeNode) return;
if (m_canvas) {
QMenu menu;
const bool singleLayer = nodes.size() == 1;
if (index.isValid()) {
menu.addAction(m_propertiesAction);
if (singleLayer) {
addActionToMenu(&menu, "layer_style");
}
{
KisSignalsBlocker b(m_colorSelector);
m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1);
}
menu.addAction(m_colorSelectorAction);
menu.addSeparator();
addActionToMenu(&menu, "cut_layer_clipboard");
addActionToMenu(&menu, "copy_layer_clipboard");
addActionToMenu(&menu, "paste_layer_from_clipboard");
menu.addAction(m_removeAction);
addActionToMenu(&menu, "duplicatelayer");
addActionToMenu(&menu, "merge_layer");
if (singleLayer) {
addActionToMenu(&menu, "flatten_image");
addActionToMenu(&menu, "flatten_layer");
}
menu.addSeparator();
QMenu *selectMenu = menu.addMenu(i18n("&Select"));
addActionToMenu(selectMenu, "select_all_layers");
addActionToMenu(selectMenu, "select_visible_layers");
addActionToMenu(selectMenu, "select_invisible_layers");
addActionToMenu(selectMenu, "select_locked_layers");
addActionToMenu(selectMenu, "select_unlocked_layers");
QMenu *groupMenu = menu.addMenu(i18n("&Group"));
addActionToMenu(groupMenu, "create_quick_group");
addActionToMenu(groupMenu, "create_quick_clipping_group");
addActionToMenu(groupMenu, "quick_ungroup");
+ QMenu *locksMenu = menu.addMenu(i18n("&Locks && visibility"));
+ addActionToMenu(locksMenu, "toggle_layer_lock");
+ addActionToMenu(locksMenu, "toggle_layer_visibility");
+ addActionToMenu(locksMenu, "toggle_layer_alpha_lock");
+ addActionToMenu(locksMenu, "toggle_layer_inherit_alpha");
if (singleLayer) {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_transparency_mask");
addActionToMenu(addLayerMenu, "add_new_filter_mask");
if (!KisConfig().disableColorizeMaskFeature()) {
addActionToMenu(addLayerMenu, "add_new_colorize_mask");
}
addActionToMenu(addLayerMenu, "add_new_transform_mask");
addActionToMenu(addLayerMenu, "add_new_selection_mask");
QMenu *convertToMenu = menu.addMenu(i18n("&Convert"));
addActionToMenu(convertToMenu, "convert_to_paint_layer");
addActionToMenu(convertToMenu, "convert_to_transparency_mask");
addActionToMenu(convertToMenu, "convert_to_filter_mask");
addActionToMenu(convertToMenu, "convert_to_selection_mask");
QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha"));
addActionToMenu(splitAlphaMenu, "split_alpha_into_mask");
addActionToMenu(splitAlphaMenu, "split_alpha_write");
addActionToMenu(splitAlphaMenu, "split_alpha_save_merged");
}
menu.addSeparator();
if (singleLayer) {
addActionToMenu(&menu, "show_in_timeline");
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node && !node->inherits("KisTransformMask")) {
addActionToMenu(&menu, "isolate_layer");
}
menu.addAction(m_selectOpaque);
}
}
menu.exec(pos);
}
}
void KisLayerBox::slotMergeLayer()
{
if (!m_canvas) return;
m_nodeManager->mergeLayer();
}
void KisLayerBox::slotMinimalView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::MinimalMode);
}
void KisLayerBox::slotDetailedView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::DetailedMode);
}
void KisLayerBox::slotThumbnailView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::ThumbnailMode);
}
void KisLayerBox::slotRmClicked()
{
if (!m_canvas) return;
m_nodeManager->removeNode();
}
void KisLayerBox::slotRaiseClicked()
{
if (!m_canvas) return;
m_nodeManager->raiseNode();
}
void KisLayerBox::slotLowerClicked()
{
if (!m_canvas) return;
m_nodeManager->lowerNode();
}
void KisLayerBox::slotPropertiesClicked()
{
if (!m_canvas) return;
if (KisNodeSP active = m_nodeManager->activeNode()) {
m_nodeManager->nodeProperties(active);
}
}
void KisLayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
if (!m_canvas) return;
QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id();
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp));
}
void KisLayerBox::slotOpacityChanged()
{
if (!m_canvas) return;
m_blockOpacityUpdate = true;
m_nodeManager->nodeOpacityChanged(m_newOpacity, true);
m_blockOpacityUpdate = false;
}
void KisLayerBox::slotOpacitySliderMoved(qreal opacity)
{
m_newOpacity = opacity;
m_opacityDelayTimer.start(200);
}
void KisLayerBox::slotCollapsed(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(true);
}
}
void KisLayerBox::slotExpanded(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(false);
}
}
void KisLayerBox::slotSelectOpaque()
{
if (!m_canvas) return;
QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque");
if (action) {
action->trigger();
}
}
void KisLayerBox::slotNodeCollapsedChanged()
{
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
}
inline bool isSelectionMask(KisNodeSP node)
{
return dynamic_cast(node.data());
}
KisNodeSP KisLayerBox::findNonHidableNode(KisNodeSP startNode)
{
if (isSelectionMask(startNode) &&
startNode->parent() &&
!startNode->parent()->parent()) {
KisNodeSP node = startNode->prevSibling();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
if (!node) {
node = startNode->nextSibling();
while (node && isSelectionMask(node)) {
node = node->nextSibling();
}
}
if (!node) {
node = m_image->root()->lastChild();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
}
KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!");
startNode = node;
}
return startNode;
}
void KisLayerBox::slotEditGlobalSelection(bool showSelections)
{
KisNodeSP lastActiveNode = m_nodeManager->activeNode();
KisNodeSP activateNode = lastActiveNode;
if (!showSelections) {
activateNode = findNonHidableNode(activateNode);
}
m_nodeModel->setShowGlobalSelection(showSelections);
if (showSelections) {
KisNodeSP newMask = m_image->rootLayer()->selectionMask();
if (newMask) {
activateNode = newMask;
}
}
if (activateNode) {
if (lastActiveNode != activateNode) {
m_nodeManager->slotNonUiActivatedNode(activateNode);
} else {
setCurrentNode(lastActiveNode);
}
}
}
void KisLayerBox::selectionChanged(const QModelIndexList selection)
{
if (!m_nodeManager) return;
/**
* When the user clears the extended selection by clicking on the
* empty area of the docker, the selection should be reset on to
* the active layer, which might be even unselected(!).
*/
if (selection.isEmpty() && m_nodeManager->activeNode()) {
QModelIndex selectedIndex =
m_filteringModel->indexFromNode(m_nodeManager->activeNode());
m_wdgLayerBox->listLayers->selectionModel()->
setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect);
return;
}
QList selectedNodes;
Q_FOREACH (const QModelIndex &idx, selection) {
selectedNodes << m_filteringModel->nodeFromIndex(idx);
}
m_nodeManager->slotSetSelectedNodes(selectedNodes);
updateUI();
}
void KisLayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end)
{
/**
* Qt has changed its behavior when deleting an item. Previously
* the selection priority was on the next item in the list, and
* now it has shanged to the previous item. Here we just adjust
* the selected item after the node removal. Please take care that
* this method overrides what was done by the corresponding method
* of QItemSelectionModel, which *has already done* its work. That
* is why we use (start - 1) and (end + 1) in the activation
* condition.
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=345601
*/
QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex();
QAbstractItemModel *model = m_filteringModel;
if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) {
QModelIndex old = currentIndex;
if (model && end < model->rowCount(parent) - 1) // there are rows left below the change
currentIndex = model->index(end + 1, old.column(), parent);
else if (start > 0) // there are rows left above the change
currentIndex = model->index(start - 1, old.column(), parent);
else // there are no rows left in the table
currentIndex = QModelIndex();
if (currentIndex.isValid() && currentIndex != old) {
m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex);
}
}
}
void KisLayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes)
{
if (!m_nodeManager) return;
QModelIndexList newSelection;
Q_FOREACH(KisNodeSP node, nodes) {
newSelection << m_filteringModel->indexFromNode(node);
}
QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel();
if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) {
return;
}
QItemSelection selection;
Q_FOREACH(const QModelIndex &idx, newSelection) {
selection.select(idx, idx);
}
model->select(selection, QItemSelectionModel::ClearAndSelect);
}
void KisLayerBox::updateThumbnail()
{
m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotRenameCurrentNode()
{
m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotColorLabelChanged(int label)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
Q_FOREACH(KisNodeSP node, nodes) {
auto applyLabelFunc =
[label](KisNodeSP node) {
node->setColorLabelIndex(label);
};
KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc);
}
}
void KisLayerBox::updateAvailableLabels()
{
if (!m_image) return;
m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root());
}
void KisLayerBox::updateLayerFiltering()
{
m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors());
}
void KisLayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel)
{
if (channel->id() == KisKeyframeChannel::Opacity.id()) {
watchOpacityChannel(channel);
}
}
void KisLayerBox::watchOpacityChannel(KisKeyframeChannel *channel)
{
if (m_opacityChannel) {
m_opacityChannel->disconnect(this);
}
m_opacityChannel = channel;
if (m_opacityChannel) {
connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
}
}
void KisLayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe)
{
Q_UNUSED(keyframe);
if (m_blockOpacityUpdate) return;
updateUI();
}
void KisLayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime)
{
Q_UNUSED(fromTime);
slotOpacityKeyframeChanged(keyframe);
}
void KisLayerBox::slotImageTimeChanged(int time)
{
Q_UNUSED(time);
updateUI();
}
#include "moc_kis_layer_box.cpp"