diff --git a/krita/krita.action b/krita/krita.action
index 483abbdc8f..6b622f150c 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,3638 +1,3650 @@
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
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
Show Krita log for bug reports.
Show Krita log for bug reports.
Show Krita log for bug reports.
false
Show system information for bug reports.
Show system information for bug reports.
Show system information for bug reports.
false
Rename Composition...
Rename Composition
Rename Composition
0
0
false
Update Composition
Update Composition
Update Composition
0
0
false
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
Use multiple of 2 for pixel scale
1
0
true
&Invert Selection
Invert current selection
Invert Selection
10000000000
100
Ctrl+Shift+I
false
Create Snapshot
Create Snapshot
1
0
false
Switch to Selected Snapshot
Switch to selected snapshot
1
0
false
Remove Selected Snapshot
Remove Selected Snapshot
1
0
false
Painting
lightness-increase
Make brush color lighter
Make brush color lighter
Make brush color lighter
0
0
L
false
lightness-decrease
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
opacity-increase
Increase opacity
Increase opacity
Increase opacity
0
0
O
false
opacity-decrease
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
0
true
symmetry-vertical
Vertical Mirror Tool
Vertical Mirror Tool
Vertical Mirror Tool
0
true
Hide Mirror X Line
Hide Mirror X Line
Hide Mirror X Line
10000
true
Hide Mirror Y Line
Hide Mirror Y Line
Hide Mirror Y Line
10000
true
Lock
Lock X Line
Lock X Line
10000
true
Lock Y Line
Lock Y Line
Lock Y Line
10000
true
Move to Canvas Center X
Move to Canvas Center X
Move to Canvas Center X
10000
false
Move to Canvas Center Y
Move to Canvas Center Y
Move to Canvas Center Y
10000
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
Selection Mode: Add
Selection Mode: Add
Selection Mode: Add
A
false
Selection Mode: Subtract
Selection Mode: Subtract
Selection Mode: Subtract
S
false
Selection Mode: Intersect
Selection Mode: Intersect
Selection Mode: Intersect
false
Selection Mode: Replace
Selection Mode: Replace
Selection Mode: Replace
R
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
brushsize-decrease
Decrease Brush Size
Decrease Brush Size
Decrease Brush Size
0
0
[
false
smoothing-basic
Brush Smoothing: Basic
Brush Smoothing: Basic
Brush Smoothing: Basic
false
brushsize-increase
Increase Brush Size
Increase Brush Size
Increase Brush Size
0
0
]
false
Toggle Snap To Assistants
Toggle Snap to Assistants
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
&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
&Invert
Invert
Invert
10000
0
Ctrl+I
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
&Phong Bumpmap...
Phong Bumpmap
Phong Bumpmap
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
Rectangle Tool
Rectangle Tool
Rectangle Tool
false
Multibrush Tool
Multibrush Tool
Multibrush Tool
Q
false
Colorize Mask Tool
Colorize Mask Tool
Colorize Mask Tool
Smart Patch Tool
Smart Patch Tool
Smart Patch Tool
Pan Tool
Pan Tool
Pan Tool
Select Shapes Tool
Select Shapes Tool
Select Shapes Tool
false
Color Picker
Select a color from the image or current layer
Select a color from the image or current layer
P
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
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
Pattern editing
Pattern editing
Pattern editing
false
Review
Review
Review
false
Draw a gradient.
Draw a gradient.
Draw a gradient.
G
false
Measurement Tool
Measure the distance between two points
Measure the distance between two points
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
Edit Shapes Tool
Edit Shapes Tool
Edit Shapes Tool
false
Zoom Tool
Zoom Tool
Zoom Tool
false
Gradient Editing Tool
Gradient editing
Gradient editing
false
Reference Images Tool
Reference Images Tool
Reference Images Tool
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
prevframe
Previous Frame
Move selection to previous frame
Move selection to previous frame
1
0
false
nextframe
Next Frame
Move selection to next frame
Move selection to next frame
1
0
false
prevkeyframe
Previous Keyframe
Move selection to previous keyframe
Move selection to previous keyframe
1
0
false
nextkeyframe
Next Keyframe
Move selection to next keyframe
Move selection to next keyframe
1
0
false
prevkeyframe
Previous Matching Keyframe
Move selection to previous keyframe of the same color
Move selection to previous matching keyframe
1
0
false
nextkeyframe
Next Matching Keyframe
Move selection to next keyframe of the same color
Move selection to next matching keyframe
1
0
false
animation_play
Play / pause animation
Play / pause animation
Play / pause animation
1
0
false
animation_stop
Stop animation
Stop animation
Stop animation
1
0
false
addblankframe
Create Blank Frame
Add blank frame
Add blank frame
100000
0
false
addduplicateframe
Create Duplicate 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
dropframe
Drop Frames
Enable to preserve playback timing.
true
Pin to Timeline
If checked, layer becomes pinned to the timeline, making it visible even when inactive.
true
Insert Keyframe Left
Insert keyframes to the left of selection, moving the tail of animation to the right.
100000
0
false
Insert Keyframe Right
Insert keyframes to the right of selection, moving the tail of animation to the right.
100000
0
false
Insert Multiple Keyframes
Insert several keyframes based on user parameters.
100000
0
false
Remove Frame and Pull
Remove keyframes moving the tail of animation to the left
100000
0
false
deletekeyframe
Remove Keyframe
Remove keyframes without moving anything around
100000
0
false
Insert Column Left
Insert column to the left of selection, moving the tail of animation to the right
100000
0
false
Insert Column Right
Insert column to the right of selection, moving the tail of animation to the right
100000
0
false
Insert Multiple Columns
Insert several columns based on user parameters.
100000
0
false
Remove Column and Pull
Remove columns moving the tail of animation to the left
100000
0
false
Remove Column
Remove columns without moving anything around
100000
0
false
Insert Hold Frame
Insert a hold frame after every keyframe
100000
0
false
Insert Multiple Hold Frames
Insert N hold frames after every keyframe
100000
0
false
Remove Hold Frame
Remove a hold frame after every keyframe
100000
0
false
Remove Multiple Hold Frames
Remove N hold frames after every keyframe
100000
0
false
Insert Hold Column
Insert a hold column into the frame at the current position
100000
0
false
Insert Multiple Hold Columns
Insert N hold columns into the frame at the current position
100000
0
false
Remove Hold Column
Remove a hold column from the frame at the current position
100000
0
false
Remove Multiple Hold Columns
Remove N hold columns from the frame at the current position
100000
0
false
Add opacity keyframe
Adds keyframe to control layer opacity
100000
0
false
Remove opacity keyframe
Removes keyframe to control layer opacity
100000
0
false
Mirror Frames
Mirror frames' position
100000
0
false
Mirror Columns
Mirror columns' position
100000
0
false
Copy to Clipboard
Copy frames to clipboard
100000
0
false
Cut to Clipboard
Cut frames to clipboard
100000
0
false
Paste from Clipboard
Paste frames from clipboard
100000
0
false
Copy Columns to Clipboard
Copy columns to clipboard
100000
0
false
Cut Columns to Clipboard
Cut columns to clipboard
100000
0
false
Paste Columns from Clipboard
Paste columns from clipboard
100000
0
false
Set Start Time
100000
0
false
Set End Time
100000
0
false
Update Playback Range
100000
0
false
Layers
Activate next layer
Activate next layer
Activate next layer
1000
0
PgUp
false
Activate next sibling layer, skipping over groups.
Activate next sibling layer
Activate next sibling layer
1000
0
false
Activate previous layer
Activate previous layer
Activate previous layer
1000
0
PgDown
false
Activate previous sibling layer, skipping over groups.
Activate previous sibling layer
Activate previous sibling layer
1000
0
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 Active Layer
Isolate Active Layer
Isolate Active Layer
1000
0
true
view-filter
&Isolate Active Group
Isolate Active Group
Isolate Active Group
1000
0
true
layer-locked
&Toggle layer lock
Toggle layer lock
Toggle layer lock
1000
0
false
visible
Toggle layer &visibility
Toggle layer visibility
Toggle layer visibility
1000
0
false
+
+ visible
+ Toggle Layer Soloing
+
+ Toggle layer soloing, temporarily disabling visibility of all non-child layers.
+ Toggle layer soloing
+ 1000
+ 0
+
+ false
+
+
transparency-locked
Toggle layer &alpha
Toggle layer alpha
Toggle layer 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
1000
0
false
Cut Layer
Cut layer to clipboard
Cut layer to clipboard
1000
0
false
Paste Layer
Paste layer from clipboard
Paste layer from clipboard
1000
0
false
Quick Group
Create a group layer containing selected layers
Quick Group
1000
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 Vector Layer as SVG...
Save Vector Layer as SVG
Save Vector Layer as SVG
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
Convert to &animated layer
Convert layer into animation frames
Convert layer into animation frames
100000
0
false
fileLayer
to &File Layer
Saves out the layers into a new image and then references that image.
Convert to File Layer
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
1000
1
false
object-rotate-right
Rotate &Layer 90° to the Right
Rotate Layer 90° to the Right
Rotate Layer 90° to the Right
1000
1
false
object-rotate-left
Rotate Layer &90° to the Left
Rotate Layer 90° to the Left
Rotate Layer 90° to the Left
1000
1
false
Rotate Layer &180°
Rotate Layer 180°
Rotate Layer 180°
1000
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
1000
1
false
symmetry-horizontal
Mirror All Layers Hori&zontally
Mirror All Layers Horizontally
Mirror All Layers Horizontally
1000
1
false
symmetry-vertical
Mirror All Layers &Vertically
Mirror All Layers Vertically
Mirror All Layers Vertically
1000
1
false
&Rotate All Layers...
Rotate All Layers
Rotate All Layers
1000
1
false
object-rotate-right
Rotate All &Layers 90° to the Right
Rotate All Layers 90° to the Right
Rotate All Layers 90° to the Right
1000
1
false
object-rotate-left
Rotate All Layers &90° to the Left
Rotate All Layers 90° to the Left
Rotate All Layers 90° to the Left
1000
1
false
Rotate All Layers &180°
Rotate All Layers 180°
Rotate All Layers 180°
1000
1
false
Scale All &Layers to new Size...
Scale All Layers to new Size
Scale All Layers to new Size
100000
1
false
&Shear All Layers...
Shear All Layers
Shear All Layers
1000
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
Set Copy F&rom...
Set the source for the selected clone layer(s).
Set Copy From
1000
1
false
diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp
index f1a93865af..2e509a5aed 100644
--- a/plugins/dockers/layerdocker/LayerBox.cpp
+++ b/plugins/dockers/layerdocker/LayerBox.cpp
@@ -1,1176 +1,1195 @@
/*
* LayerBox.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 "LayerBox.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
#include
#include
#include
#include "kis_action_manager.h"
#include "widgets/kis_cmb_composite.h"
#include "kis_slider_spin_box.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_node_model.h"
#include "canvas/kis_canvas2.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 "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_selection.h"
#include "kis_processing_applicator.h"
#include "commands/kis_set_global_selection_command.h"
#include "KisSelectionActionsAdapter.h"
#include "kis_layer_utils.h"
#include "ui_WdgLayerBox.h"
#include "NodeView.h"
#include "SyncButtonAndAction.h"
class LayerBoxStyle : public QProxyStyle
{
public:
LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {}
void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
if (element == QStyle::PE_IndicatorItemViewItemDrop)
{
QColor color(widget->palette().color(QPalette::Highlight).lighter());
if (option->rect.height() == 0) {
QBrush brush(color);
QRect r(option->rect);
r.setTop(r.top() - 2);
r.setBottom(r.bottom() + 2);
painter->fillRect(r, brush);
} else {
color.setAlpha(200);
QBrush brush(color);
painter->fillRect(option->rect, brush);
}
}
else
{
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
};
inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id)
{
if (!viewManager || !button) return;
KisAction *action = viewManager->actionManager()->actionByName(id);
if (!action) return;
connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool)));
connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons()));
}
inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id)
{
if (m_canvas) {
menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id));
}
}
LayerBox::LayerBox()
: QDockWidget(i18n("Layers"))
, m_canvas(0)
, m_wdgLayerBox(new Ui_WdgLayerBox)
, m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_colorLabelCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE)
{
KisConfig cfg(false);
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_opacityDelayTimer.setSingleShot(true);
m_wdgLayerBox->setupUi(mainWidget);
m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style()));
connect(m_wdgLayerBox->listLayers,
SIGNAL(contextMenuRequested(QPoint,QModelIndex)),
this, SLOT(slotContextMenuRequested(QPoint,QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList)));
slotUpdateIcons();
m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setEnabled(false);
m_wdgLayerBox->bnRaise->setEnabled(false);
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(i18n("%"));
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_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(QModelIndex,int,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset()));
connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode()));
connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode()));
connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode()));
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);
MouseClickIgnore* mouseEater = new MouseClickIgnore(this);
m_colorSelector->installEventFilter(mouseEater);
connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int)));
m_colorSelectorAction = new QWidgetAction(this);
m_colorSelectorAction->setDefaultWidget(m_colorSelector);
connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,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, &LayerBox::slotAboutToRemoveRows);
//LayerFilter Menu
QMenu *layerFilterMenu = new QMenu(this);
m_wdgLayerBox->bnLayerFilters->setMenu(layerFilterMenu);
m_wdgLayerBox->bnLayerFilters->setPopupMode(QToolButton::InstantPopup);
const QIcon filterIcon = KisIconUtils::loadIcon("view-filter");
m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon);
QPixmap filterEnabledPixmap = filterIcon.pixmap(64,64);
const QBitmap filterEnabledBitmask = filterEnabledPixmap.mask();
filterEnabledPixmap.fill(palette().color(QPalette::Highlight));
filterEnabledPixmap.setMask(filterEnabledBitmask);
const QIcon filterEnabledIcon = QIcon(filterEnabledPixmap);
layerFilterWidget = new KisLayerFilterWidget(this);
connect(layerFilterWidget, SIGNAL(filteringOptionsChanged()), this, SLOT(updateLayerFiltering()));
connect(layerFilterWidget, &KisLayerFilterWidget::filteringOptionsChanged, [this, filterIcon, filterEnabledIcon](){
if(layerFilterWidget->isCurrentlyFiltering()) {
m_wdgLayerBox->bnLayerFilters->setIcon(filterEnabledIcon);
} else {
m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon);
}
m_wdgLayerBox->bnLayerFilters->setSelectedColors(QList::fromSet(layerFilterWidget->getActiveColors()));
m_wdgLayerBox->bnLayerFilters->setTextFilter(layerFilterWidget->hasTextFilter());
});
QWidgetAction *layerFilterMenuAction = new QWidgetAction(this);
layerFilterMenuAction->setDefaultWidget(layerFilterWidget);
layerFilterMenu->addAction(layerFilterMenuAction);
setEnabled(false);
connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels()));
// set up the configure menu for changing thumbnail size
QMenu* configureMenu = new QMenu(this);
configureMenu->setStyleSheet("margin: 6px");
configureMenu->addSection(i18n("Thumbnail Size"));
m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu);
m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure"));
m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup);
// add horizontal slider
thumbnailSizeSlider = new QSlider(this);
thumbnailSizeSlider->setOrientation(Qt::Horizontal);
thumbnailSizeSlider->setRange(20, 80);
thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc
thumbnailSizeSlider->setMinimumHeight(20);
thumbnailSizeSlider->setMinimumWidth(40);
thumbnailSizeSlider->setTickInterval(5);
QWidgetAction *sliderAction= new QWidgetAction(this);
sliderAction->setDefaultWidget(thumbnailSizeSlider);
configureMenu->addAction(sliderAction);
connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start()));
connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize()));
}
LayerBox::~LayerBox()
{
delete m_wdgLayerBox;
}
void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, NodeView *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->collapsed() && node->childCount() > 0) {
expandNodesRecursively(node, filteringModel, nodeView);
}
node = node->nextSibling();
}
nodeView->blockSignals(false);
}
void LayerBox::slotAddLayerBnClicked()
{
if (m_canvas) {
KisNodeList nodes = m_nodeManager->selectedNodes();
if (nodes.size() == 1) {
KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer");
action->trigger();
} else {
KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group");
action->trigger();
}
}
}
void LayerBox::setViewManager(KisViewManager* kisview)
{
m_nodeManager = kisview->nodeManager();
if (m_nodeManager) {
connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode()));
}
Q_FOREACH (KisAction *action, m_actions) {
kisview->actionManager()->
addAction(action->objectName(),
action);
}
connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked()));
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()));
m_changeCloneSourceAction = actionManager->createAction("set-copy-from");
Q_ASSERT(m_changeCloneSourceAction);
connect(m_changeCloneSourceAction, &KisAction::triggered,
this, &LayerBox::slotChangeCloneSourceClicked);
+
+ m_layerToggleSolo = actionManager->createAction("toggle_layer_soloing");
+ connect(m_layerToggleSolo, SIGNAL(triggered(bool)), this, SLOT(toggleActiveLayerSolo()));
}
void LayerBox::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);
m_selectionActionsAdapter.reset();
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();
emit imageChanged();
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_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager()));
m_nodeModel->setDummiesFacade(kritaDummiesFacade,
m_image,
kritaShapeController,
m_selectionActionsAdapter.data(),
m_nodeManager);
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 -> LayerBox
connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
this, SLOT(setCurrentNode(KisNodeSP)));
connect(m_nodeManager,
SIGNAL(sigUiNeedChangeSelectedNodes(QList)),
SLOT(slotNodeManagerChangedSelection(QList)));
}
else {
setCurrentNode(m_canvas->imageView()->currentNode());
}
// Connection LayerBox -> KisNodeManager (isolate layer)
connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
m_nodeManager, SLOT(toggleIsolateActiveNode()));
KisImageAnimationInterface *animation = m_image->animationInterface();
connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::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");
addActionToMenu(m_newLayerMenu, "add_new_colorize_mask");
addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
}
}
void LayerBox::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 LayerBox::notifyImageDeleted()
{
setCanvas(0);
}
void LayerBox::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, &LayerBox::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));
m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace());
if (activeNode) {
if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) {
m_wdgLayerBox->doubleOpacity->setEnabled(true);
if (!m_wdgLayerBox->doubleOpacity->isDragging()) {
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 called *only* when non-GUI code requested the
* change of the current node
*/
void LayerBox::setCurrentNode(KisNodeSP node)
{
m_filteringModel->setActiveNode(node);
QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex();
m_filteringModel->setData(index, true, KisNodeModel::ActiveRole);
updateUI();
}
void LayerBox::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 LayerBox::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);
}
// range: 0-100
void LayerBox::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 LayerBox::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");
}
Q_FOREACH(KisNodeSP node, nodes) {
if (node && node->inherits("KisCloneLayer")) {
menu.addAction(m_changeCloneSourceAction);
break;
}
}
{
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");
addActionToMenu(&menu, "new_from_visible");
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("&Toggle Locks && Visibility"));
addActionToMenu(locksMenu, "toggle_layer_visibility");
addActionToMenu(locksMenu, "toggle_layer_lock");
addActionToMenu(locksMenu, "toggle_layer_inherit_alpha");
addActionToMenu(locksMenu, "toggle_layer_alpha_lock");
if (singleLayer) {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_transparency_mask");
addActionToMenu(addLayerMenu, "add_new_filter_mask");
addActionToMenu(addLayerMenu, "add_new_colorize_mask");
addActionToMenu(addLayerMenu, "add_new_transform_mask");
addActionToMenu(addLayerMenu, "add_new_selection_mask");
addLayerMenu->addSeparator();
addActionToMenu(addLayerMenu, "add_new_clone_layer");
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");
addActionToMenu(convertToMenu, "convert_to_file_layer");
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");
} else {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_clone_layer");
}
menu.addSeparator();
addActionToMenu(&menu, "pin_to_timeline");
if (singleLayer) {
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node && !node->inherits("KisTransformMask")) {
addActionToMenu(&menu, "isolate_active_layer");
addActionToMenu(&menu, "isolate_active_group");
}
addActionToMenu(&menu, "selectopaque");
}
}
menu.exec(pos);
}
}
void LayerBox::slotMinimalView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode);
}
void LayerBox::slotDetailedView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode);
}
void LayerBox::slotThumbnailView()
{
m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode);
}
void LayerBox::slotRmClicked()
{
if (!m_canvas) return;
m_nodeManager->removeNode();
}
void LayerBox::slotRaiseClicked()
{
if (!m_canvas) return;
m_nodeManager->raiseNode();
}
void LayerBox::slotLowerClicked()
{
if (!m_canvas) return;
m_nodeManager->lowerNode();
}
void LayerBox::slotPropertiesClicked()
{
if (!m_canvas) return;
if (KisNodeSP active = m_nodeManager->activeNode()) {
m_nodeManager->nodeProperties(active);
}
}
void LayerBox::slotChangeCloneSourceClicked()
{
if (!m_canvas) return;
m_nodeManager->changeCloneSource();
}
void LayerBox::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 LayerBox::slotOpacityChanged()
{
if (!m_canvas) return;
m_blockOpacityUpdate = true;
m_nodeManager->nodeOpacityChanged(m_newOpacity);
m_blockOpacityUpdate = false;
}
void LayerBox::slotOpacitySliderMoved(qreal opacity)
{
m_newOpacity = opacity;
m_opacityDelayTimer.start(200);
}
void LayerBox::slotCollapsed(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(true);
}
}
void LayerBox::slotExpanded(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(false);
}
}
void LayerBox::slotSelectOpaque()
{
if (!m_canvas) return;
QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque");
if (action) {
action->trigger();
}
}
void LayerBox::slotNodeCollapsedChanged()
{
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
}
inline bool isSelectionMask(KisNodeSP node)
{
return dynamic_cast(node.data());
}
KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode)
{
if (KisNodeManager::isNodeHidden(startNode, true) &&
startNode->parent() &&
!startNode->parent()->parent()) {
KisNodeSP node = startNode->prevSibling();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->prevSibling();
}
if (!node) {
node = startNode->nextSibling();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->nextSibling();
}
}
if (!node) {
node = m_image->root()->lastChild();
while (node && KisNodeManager::isNodeHidden(node, true)) {
node = node->prevSibling();
}
}
KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!");
startNode = node;
}
return startNode;
}
void LayerBox::slotEditGlobalSelection(bool showSelections)
{
KisNodeSP lastActiveNode = m_nodeManager->activeNode();
KisNodeSP activateNode = lastActiveNode;
KisSelectionMaskSP globalSelectionMask;
if (!showSelections) {
activateNode =
m_savedNodeBeforeEditSelectionMode ?
KisNodeSP(m_savedNodeBeforeEditSelectionMode) :
findNonHidableNode(activateNode);
}
m_nodeModel->setShowGlobalSelection(showSelections);
globalSelectionMask = m_image->rootLayer()->selectionMask();
// try to find deactivated, but visible masks
if (!globalSelectionMask) {
KoProperties properties;
properties.setProperty("visible", true);
QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties);
if (!masks.isEmpty()) {
globalSelectionMask = dynamic_cast(masks.first().data());
}
}
// try to find at least any selection mask
if (!globalSelectionMask) {
KoProperties properties;
QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties);
if (!masks.isEmpty()) {
globalSelectionMask = dynamic_cast(masks.first().data());
}
}
if (globalSelectionMask) {
if (showSelections) {
activateNode = globalSelectionMask;
}
}
if (activateNode != lastActiveNode) {
m_nodeManager->slotNonUiActivatedNode(activateNode);
} else if (lastActiveNode) {
setCurrentNode(lastActiveNode);
}
if (showSelections && !globalSelectionMask) {
KisProcessingApplicator applicator(m_image, 0,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Quick Selection Mask"));
applicator.applyCommand(
new KisLayerUtils::KeepNodesSelectedCommand(
m_nodeManager->selectedNodes(), KisNodeList(),
lastActiveNode, 0, m_image, false),
KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.end();
} else if (!showSelections &&
globalSelectionMask &&
globalSelectionMask->selection()->selectedRect().isEmpty()) {
KisProcessingApplicator applicator(m_image, 0,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Cancel Quick Selection Mask"));
applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
if (showSelections) {
m_savedNodeBeforeEditSelectionMode = lastActiveNode;
}
}
void LayerBox::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 LayerBox::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 (model && 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 LayerBox::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 LayerBox::updateThumbnail()
{
m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
}
void LayerBox::slotRenameCurrentNode()
{
m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex());
}
void LayerBox::slotColorLabelChanged(int label)
{
KisNodeList selectedNodes = m_nodeManager->selectedNodes();
Q_FOREACH(KisNodeSP selectedNode, selectedNodes) {
//Always apply label to selected nodes..
selectedNode->setColorLabelIndex(label);
//Apply label only to unlabelled children..
KisNodeList children = selectedNode->childNodes(QStringList(), KoProperties());
auto applyLabelFunc =
[label](KisNodeSP child) {
if (child->colorLabelIndex() == 0) {
child->setColorLabelIndex(label);
}
};
Q_FOREACH(KisNodeSP child, children) {
KisLayerUtils::recursiveApplyNodes(child, applyLabelFunc);
}
}
}
void LayerBox::updateAvailableLabels()
{
if (!m_image) return;
layerFilterWidget->updateColorLabels(m_image->root());
}
void LayerBox::updateLayerFiltering()
{
m_filteringModel->setAcceptedLabels(layerFilterWidget->getActiveColors());
m_filteringModel->setTextFilter(layerFilterWidget->getTextFilter());
}
void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel)
{
if (channel->id() == KisKeyframeChannel::Opacity.id()) {
watchOpacityChannel(channel);
}
}
void LayerBox::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 LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe)
{
Q_UNUSED(keyframe);
if (m_blockOpacityUpdate) return;
updateUI();
}
void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime)
{
Q_UNUSED(fromTime);
slotOpacityKeyframeChanged(keyframe);
}
void LayerBox::slotImageTimeChanged(int time)
{
Q_UNUSED(time);
updateUI();
}
void LayerBox::slotForgetAboutSavedNodeBeforeEditSelectionMode()
{
m_savedNodeBeforeEditSelectionMode = 0;
}
void LayerBox::slotUpdateIcons() {
m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr"));
m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown"));
m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties"));
m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
// call child function about needing to update icons
m_wdgLayerBox->listLayers->slotUpdateIcons();
}
+void LayerBox::toggleActiveLayerSolo() {
+ NodeView* view = m_wdgLayerBox->listLayers;
+ if (!view)
+ return;
+
+ KisNodeSP node = m_nodeManager->activeNode();
+ if (!node)
+ return;
+
+ QModelIndex index = m_filteringModel->indexFromNode(node);
+ if (!index.isValid())
+ return;
+
+ view->toggleSolo(index);
+}
+
void LayerBox::slotUpdateThumbnailIconSize()
{
KisConfig cfg(false);
cfg.setLayerThumbnailSize(thumbnailSizeSlider->value());
// this is a hack to force the layers list to update its display and
// re-layout all the layers with the new thumbnail size
resize(this->width()+1, this->height()+1);
resize(this->width()-1, this->height()-1);
}
#include "moc_LayerBox.cpp"
diff --git a/plugins/dockers/layerdocker/LayerBox.h b/plugins/dockers/layerdocker/LayerBox.h
index 766564fbaf..d24187c133 100644
--- a/plugins/dockers/layerdocker/LayerBox.h
+++ b/plugins/dockers/layerdocker/LayerBox.h
@@ -1,209 +1,211 @@
/*
* LayerBox.h - 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-2009 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_LAYERBOX_H
#define KIS_LAYERBOX_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_action.h"
#include "KisViewManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_signal_compressor.h"
#include "kis_layer_filter_widget.h"
#include
class QModelIndex;
typedef QList QModelIndexList;
class QMenu;
class QAbstractButton;
class KoCompositeOp;
class KisCanvas2;
class KisNodeModel;
class KisNodeFilterProxyModel;
class Ui_WdgLayerBox;
class KisNodeJugglerCompressed;
class KisColorLabelSelectorWidget;
class QWidgetAction;
class KisKeyframeChannel;
class KisSelectionActionsAdapter;
/**
* A widget that shows a visualization of the layer structure.
*
* The center of the layer box is KisNodeModel, which shows the actual layers.
* This widget adds docking functionality and command buttons.
*
*/
class LayerBox : public QDockWidget, public KisMainwindowObserver
{
Q_OBJECT
public:
LayerBox();
~LayerBox() override;
QString observerName() override { return "LayerBox"; }
/// reimplemented from KisMainwindowObserver
void setViewManager(KisViewManager* kisview) override;
void setCanvas(KoCanvasBase *canvas) override;
void unsetCanvas() override;
private Q_SLOTS:
void notifyImageDeleted();
void slotContextMenuRequested(const QPoint &pos, const QModelIndex &index);
void slotMinimalView();
void slotDetailedView();
void slotThumbnailView();
// From the node manager to the layerbox
void slotSetCompositeOp(const KoCompositeOp* compositeOp);
void slotSetOpacity(double opacity);
void updateUI();
void setCurrentNode(KisNodeSP node);
void slotModelReset();
// from the layerbox to the node manager
void slotRmClicked();
void slotRaiseClicked();
void slotLowerClicked();
void slotPropertiesClicked();
void slotChangeCloneSourceClicked();
void slotCompositeOpChanged(int index);
void slotOpacityChanged();
void slotOpacitySliderMoved(qreal opacity);
void slotCollapsed(const QModelIndex &index);
void slotExpanded(const QModelIndex &index);
void slotSelectOpaque();
void slotNodeCollapsedChanged();
void slotEditGlobalSelection(bool showSelections);
void slotRenameCurrentNode();
void slotAboutToRemoveRows(const QModelIndex &parent, int first, int last);
void selectionChanged(const QModelIndexList selection);
void slotNodeManagerChangedSelection(const QList &nodes);
void slotColorLabelChanged(int index);
void slotUpdateIcons();
+ void toggleActiveLayerSolo();
void slotAddLayerBnClicked();
void updateThumbnail();
void updateAvailableLabels();
void updateLayerFiltering();
void slotUpdateThumbnailIconSize();
// Opacity keyframing
void slotKeyframeChannelAdded(KisKeyframeChannel *channel);
void slotOpacityKeyframeChanged(KisKeyframeSP keyframe);
void slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime);
void slotImageTimeChanged(int time);
void slotForgetAboutSavedNodeBeforeEditSelectionMode();
Q_SIGNALS:
void imageChanged();
private:
inline void connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id);
inline void addActionToMenu(QMenu *menu, const QString &id);
void watchOpacityChannel(KisKeyframeChannel *channel);
KisNodeSP findNonHidableNode(KisNodeSP startNode);
private:
QPointer m_canvas;
QScopedPointer m_selectionActionsAdapter;
QMenu *m_newLayerMenu;
KisImageWSP m_image;
QPointer m_nodeModel;
QPointer m_filteringModel;
QPointer m_nodeManager;
QPointer m_colorSelector;
QPointer m_colorSelectorAction;
Ui_WdgLayerBox* m_wdgLayerBox;
QTimer m_opacityDelayTimer;
int m_newOpacity;
QVector m_actions;
KisAction* m_removeAction;
KisAction* m_propertiesAction;
KisAction* m_changeCloneSourceAction;
+ KisAction* m_layerToggleSolo;
KisSignalCompressor m_thumbnailCompressor;
KisSignalCompressor m_colorLabelCompressor;
KisSignalCompressor m_thumbnailSizeCompressor;
KisLayerFilterWidget* layerFilterWidget;
QSlider* thumbnailSizeSlider;
KisNodeSP m_activeNode;
KisNodeWSP m_savedNodeBeforeEditSelectionMode;
QPointer m_opacityChannel;
bool m_blockOpacityUpdate {false};
};
class LayerBoxFactory : public KoDockFactoryBase
{
public:
LayerBoxFactory() { }
QString id() const override {
return QString("KisLayerBox");
}
QDockWidget* createDockWidget() override {
LayerBox * dockWidget = new LayerBox();
dockWidget->setObjectName(id());
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
#endif // KIS_LAYERBOX_H
diff --git a/plugins/dockers/layerdocker/NodeDelegate.cpp b/plugins/dockers/layerdocker/NodeDelegate.cpp
index f39402649a..c32ecd059d 100644
--- a/plugins/dockers/layerdocker/NodeDelegate.cpp
+++ b/plugins/dockers/layerdocker/NodeDelegate.cpp
@@ -1,1223 +1,1229 @@
/*
Copyright (c) 2006 Gábor Lehel
Copyright (c) 2008 Cyrille Berger
Copyright (c) 2011 José Luis Vergara
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kis_config.h"
#include "NodeDelegate.h"
#include "kis_node_model.h"
#include "NodeToolTip.h"
#include "NodeView.h"
#include "KisPart.h"
#include "input/kis_input_manager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_node_view_color_scheme.h"
#include "kis_icon_utils.h"
#include "kis_layer_properties_icons.h"
#include "krita_utils.h"
#include "kis_config_notifier.h"
typedef KisBaseNode::Property* OptionalProperty;
#include
class NodeDelegate::Private
{
public:
Private() : view(0), edit(0) { }
NodeView *view;
QPointer edit;
NodeToolTip tip;
QColor checkersColor1;
QColor checkersColor2;
QList shiftClickedIndexes;
enum StasisOperation {
Record,
Review,
Restore
};
QList rightmostProperties(const KisBaseNode::PropertyList &props) const;
int numProperties(const QModelIndex &index) const;
OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const;
OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const;
void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, const Qt::KeyboardModifiers modifier, const QModelIndex &index);
void togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList &items, StasisOperation record, bool mode);
bool stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on = false, bool off = false);
void resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
void restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
bool checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty);
void getParentsIndex(QList &items, const QModelIndex &index);
void getChildrenIndex(QList &items, const QModelIndex &index);
void getSiblingsIndex(QList &items, const QModelIndex &index);
};
NodeDelegate::NodeDelegate(NodeView *view, QObject *parent)
: QAbstractItemDelegate(parent)
, d(new Private)
{
d->view = view;
QApplication::instance()->installEventFilter(this);
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(this, SIGNAL(resetVisibilityStasis()), SLOT(slotResetState()));
slotConfigChanged();
}
NodeDelegate::~NodeDelegate()
{
delete d;
}
QSize NodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
return QSize(option.rect.width(), scm.rowHeight());
}
void NodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
{
p->save();
{
QStyleOptionViewItem option = getOptions(o, index);
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool();
if (shouldGrayOut) {
option.state &= ~QStyle::State_Enabled;
}
p->setFont(option.font);
drawColorLabel(p, option, index);
drawFrame(p, option, index);
drawThumbnail(p, option, index);
drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment)
drawIcons(p, option, index);
drawVisibilityIconHijack(p, option, index); // TODO hide when dragging
drawDecoration(p, option, index);
drawExpandButton(p, option, index);
drawBranch(p, option, index);
drawProgressBar(p, option, index);
}
p->restore();
}
void NodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QModelIndex tmp = index.parent();
// there is no indention if we have no parent group, so don't draw a branch
if (!tmp.isValid()) return;
KisNodeViewColorScheme scm;
int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1;
QRect baseRect = scm.relThumbnailRect();
// Move to current index
baseRect.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
baseRect.moveLeft(option.rect.topRight().x());
} else {
baseRect.moveRight(option.rect.topLeft().x());
}
QPoint base = baseRect.adjusted(rtlNum*scm.indentation(), 0,
rtlNum*scm.indentation(), 0).center() + QPoint(0, scm.iconSize()/4);
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setOpacity(1.0);
QColor color = scm.gridColor(option, d->view);
QColor bgColor = option.state & QStyle::State_Selected ?
qApp->palette().color(QPalette::Base) :
qApp->palette().color(QPalette::Text);
color = KritaUtils::blendColors(color, bgColor, 0.9);
// TODO: if we are a mask type, use dotted lines for the branch style
// p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin));
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
QPoint p2 = base - QPoint(rtlNum*(scm.iconSize()/2), 0);
QPoint p3 = base - QPoint(0, scm.iconSize()/2);
p->drawLine(base, p2);
p->drawLine(base, p3);
// draw parent lines (keep drawing until x position is less than 0
QPoint parentBase1 = base + QPoint(rtlNum*scm.indentation(), 0);
QPoint parentBase2 = p3 + QPoint(rtlNum*scm.indentation(), 0);
// indent lines needs to be very subtle to avoid making the docker busy looking
color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
if (tmp.isValid()) {
tmp = tmp.parent(); // Ignore the first group as it was already painted
}
while (tmp.isValid()) {
p->drawLine(parentBase1, parentBase2);
parentBase1 += QPoint(rtlNum*scm.indentation(), 0);
parentBase2 += QPoint(rtlNum*scm.indentation(), 0);
tmp = tmp.parent();
}
p->setPen(oldPen);
p->setOpacity(oldOpacity);
}
void NodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt();
QColor color = scm.colorLabel(label);
if (color.alpha() <= 0) return;
QColor bgColor = qApp->palette().color(QPalette::Base);
if ((option.state & QStyle::State_MouseOver) && !(option.state & QStyle::State_Selected)) {
color = KritaUtils::blendColors(color, bgColor, 0.6);
} else {
color = KritaUtils::blendColors(color, bgColor, 0.3);
}
QRect optionRect = option.rect.adjusted(0, 0, scm.indentation(), 0);
if (option.state & QStyle::State_Selected) {
optionRect = iconsRect(option, index);
}
if (option.direction == Qt::RightToLeft) {
optionRect.moveLeft(option.rect.topLeft().x());
} else {
optionRect.moveRight(option.rect.topRight().x());
}
p->fillRect(optionRect, color);
}
void NodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QPen oldPen = p->pen();
p->setPen(scm.gridColor(option, d->view));
const QRect visibilityRect = visibilityClickRect(option, index);
const QRect thumbnailRect = thumbnailClickRect(option, index);
const QRect decorationRect = decorationClickRect(option, index);
const QRect iconsRectR = iconsRect(option, index);
const float topY = thumbnailRect.topLeft().y();
const float bottomY = thumbnailRect.bottomLeft().y();
QPoint bottomLeftPoint;
QPoint bottomRightPoint;
if (option.direction == Qt::RightToLeft) {
bottomLeftPoint = iconsRectR.bottomLeft();
bottomRightPoint = visibilityRect.bottomRight();
} else {
bottomLeftPoint = visibilityRect.bottomLeft();
bottomRightPoint = iconsRectR.bottomRight();
}
// bottom running horizontal line
p->drawLine(bottomLeftPoint.x(), bottomY,
bottomRightPoint.x(), bottomY);
// visibility icon vertical line - left
p->drawLine(visibilityRect.topLeft().x()-1, topY,
visibilityRect.bottomLeft().x()-1, bottomY);
// visibility icon vertical line - right
p->drawLine(visibilityRect.topRight().x()+1, topY,
visibilityRect.bottomRight().x()+1, bottomY);
// thumbnail vertical line - left
p->drawLine(thumbnailRect.topLeft().x(), topY,
thumbnailRect.bottomLeft().x(), bottomY);
// thumbnail vertical line - right
p->drawLine(thumbnailRect.topRight().x(), topY,
thumbnailRect.bottomRight().x(), bottomY);
// decoration vertical line - left
p->drawLine(decorationRect.topLeft().x(), topY,
decorationRect.bottomLeft().x(), bottomY);
// decoration vertical line - right
p->drawLine(decorationRect.topRight().x(), topY,
decorationRect.bottomRight().x(), bottomY);
// icons' lines are drawn by drawIcons
//// For debugging purposes only
p->setPen(Qt::blue);
//KritaUtils::renderExactRect(p, iconsRectR);
//KritaUtils::renderExactRect(p, textRect(option, index));
//KritaUtils::renderExactRect(p, visibilityRect);
p->setPen(oldPen);
}
QRect NodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relThumbnailRect();
// Move to current index
rc.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
rc.moveLeft(option.rect.topRight().x());
} else {
rc.moveRight(option.rect.topLeft().x());
}
return rc;
}
void NodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int thumbSize = scm.thumbnailSize();
const qreal oldOpacity = p->opacity(); // remember previous opacity
QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value();
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
// paint in a checkerboard pattern behind the layer contents to represent transparent
const int step = scm.thumbnailSize() / 6;
QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32);
QPainter gc(&checkers);
gc.fillRect(QRect(0, 0, step, step), d->checkersColor1);
gc.fillRect(QRect(step, 0, step, step), d->checkersColor2);
gc.fillRect(QRect(step, step, step, step), d->checkersColor1);
gc.fillRect(QRect(0, step, step, step), d->checkersColor2);
QRect fitRect = thumbnailClickRect(option, index);
// Shrink to icon rect
fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border()));
QPoint offset;
offset.setX((fitRect.width() - img.width()) / 2);
offset.setY((fitRect.height() - img.height()) / 2);
offset += fitRect.topLeft();
QBrush brush(checkers);
p->setBrushOrigin(offset);
p->fillRect(img.rect().translated(offset), brush);
p->drawImage(offset, img);
p->setOpacity(oldOpacity); // restore old opacity
QRect borderRect = kisGrowRect(img.rect(), 1).translated(offset);
KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view));
}
QRect NodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
int propCount = d->numProperties(index);
const int iconsWidth =
propCount * (scm.iconSize() + 2 * scm.iconMargin()) +
(propCount + 1) * scm.border();
QRect fitRect = QRect(0, 0,
iconsWidth, scm.rowHeight() - scm.border());
// Move to current index
fitRect.moveTop(option.rect.topLeft().y());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
fitRect.moveLeft(option.rect.topLeft().x());
} else {
fitRect.moveRight(option.rect.topRight().x());
}
return fitRect;
}
QRect NodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
static QFont f;
static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
if (minbearing == 2003 || f != option.font) {
f = option.font; //getting your bearings can be expensive, so we cache them
minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
}
const QRect decoRect = decorationClickRect(option, index);
const QRect iconRect = iconsRect(option, index);
QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(),
(option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft());
rc.adjust(-(scm.border()+minbearing), 0,
(scm.border()+minbearing), 0);
return rc;
}
void NodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0,
-scm.textMargin(), 0);
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setPen(option.palette.color(QPalette::Active,QPalette::Text ));
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.55);
}
const QString text = index.data(Qt::DisplayRole).toString();
const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width());
p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
p->setPen(oldPen); // restore pen settings
p->setOpacity(oldOpacity);
}
QList NodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const
{
QList list;
QList prependList;
list << OptionalProperty(0);
list << OptionalProperty(0);
list << OptionalProperty(0);
KisBaseNode::PropertyList::const_iterator it = props.constBegin();
KisBaseNode::PropertyList::const_iterator end = props.constEnd();
for (; it != end; ++it) {
if (!it->isMutable) continue;
if (it->id == KisLayerPropertiesIcons::visible.id()) {
// noop...
} else if (it->id == KisLayerPropertiesIcons::locked.id()) {
list[0] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) {
list[1] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) {
list[2] = OptionalProperty(&(*it));
} else {
prependList.prepend(OptionalProperty(&(*it)));
}
}
{
QMutableListIterator i(prependList);
i.toBack();
while (i.hasPrevious()) {
OptionalProperty val = i.previous();
int emptyIndex = list.lastIndexOf(0);
if (emptyIndex < 0) break;
list[emptyIndex] = val;
i.remove();
}
}
return prependList + list;
}
int NodeDelegate::Private::numProperties(const QModelIndex &index) const
{
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
QList realProps = rightmostProperties(props);
return realProps.size();
}
OptionalProperty NodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == refProp->id) {
return &(*it);
}
}
return 0;
}
OptionalProperty NodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == KisLayerPropertiesIcons::visible.id()) {
return &(*it);
}
}
return 0;
}
void NodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index)
{
QModelIndex root(view->rootIndex());
if ((modifier & Qt::ShiftModifier) == Qt::ShiftModifier && clickedProperty->canHaveStasis) {
bool mode = true;
OptionalProperty prop = findProperty(props, clickedProperty);
// XXX: Change to use NodeProperty
int position = shiftClickedIndexes.indexOf(index);
StasisOperation record = (!prop->isInStasis)? StasisOperation::Record :
(position < 0) ? StasisOperation::Review : StasisOperation::Restore;
shiftClickedIndexes.clear();
shiftClickedIndexes.push_back(index);
QList items;
if (modifier == (Qt::ControlModifier | Qt::ShiftModifier)) {
mode = false; // inverted mode
items.insert(0, index); // important!
getSiblingsIndex(items, index);
} else {
getParentsIndex(items, index);
getChildrenIndex(items, index);
}
togglePropertyRecursive(root, clickedProperty, items, record, mode);
} else {
// If we have properties in stasis, we need to cancel stasis to avoid overriding
// values in stasis.
// IMPORTANT -- we also need to check the first row of nodes to determine
// if a stasis is currently active in some cases.
const bool hasPropInStasis = (shiftClickedIndexes.count() > 0 || checkImmediateStasis(root, clickedProperty));
if (clickedProperty->canHaveStasis && hasPropInStasis) {
shiftClickedIndexes.clear();
restorePropertyInStasisRecursive(root, clickedProperty);
} else {
shiftClickedIndexes.clear();
resetPropertyStateRecursive(root, clickedProperty);
clickedProperty->state = !clickedProperty->state.toBool();
clickedProperty->isInStasis = false;
view->model()->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
}
}
}
void NodeDelegate::Private::togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList &items, StasisOperation record, bool mode)
{
int rowCount = view->model()->rowCount(root);
for (int i = 0; i < rowCount; i++) {
QModelIndex idx = view->model()->index(i, 0, root);
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = findProperty(props, clickedProperty);
if (!prop) continue;
if (record == StasisOperation::Record) {
prop->stateInStasis = prop->state.toBool();
}
if (record == StasisOperation::Review || record == StasisOperation::Record) {
prop->isInStasis = true;
if(mode) { //include mode
prop->state = (items.contains(idx)) ? QVariant(true) : QVariant(false);
} else { // exclude
prop->state = (!items.contains(idx))? prop->state :
(items.at(0) == idx)? QVariant(true) : QVariant(false);
}
} else { // restore
prop->state = QVariant(prop->stateInStasis);
prop->isInStasis = false;
}
view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
togglePropertyRecursive(idx,clickedProperty, items, record, mode);
}
}
bool NodeDelegate::Private::stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on, bool off)
{
int rowCount = view->model()->rowCount(root);
bool result = false;
for (int i = 0; i < rowCount; i++) {
if (result) break; // return on first find
QModelIndex idx = view->model()->index(i, 0, root);
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = findProperty(props, clickedProperty);
if (!prop) continue;
if (prop->isInStasis) {
on = true;
} else {
off = true;
}
// stop if both states exist
if (on && off) {
return true;
}
result = stasisIsDirty(idx,clickedProperty, on, off);
}
return result;
}
void NodeDelegate::Private::resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
{
if (!clickedProperty->canHaveStasis) return;
int rowCount = view->model()->rowCount(root);
for (int i = 0; i < rowCount; i++) {
QModelIndex idx = view->model()->index(i, 0, root);
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = findProperty(props, clickedProperty);
if (!prop) continue;
prop->isInStasis = false;
view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
resetPropertyStateRecursive(idx,clickedProperty);
}
}
void NodeDelegate::Private::restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
{
if (!clickedProperty->canHaveStasis) return;
int rowCount = view->model()->rowCount(root);
for (int i = 0; i < rowCount; i++) {
QModelIndex idx = view->model()->index(i, 0, root);
KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = findProperty(props, clickedProperty);
if (prop->isInStasis) {
prop->isInStasis = false;
prop->state = QVariant(prop->stateInStasis);
}
view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
restorePropertyInStasisRecursive(idx, clickedProperty);
}
}
bool NodeDelegate::Private::checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty)
{
if (!clickedProperty->canHaveStasis) return false;
const int rowCount = view->model()->rowCount(root);
for (int i = 0; i < rowCount; i++){
QModelIndex idx = view->model()->index(i, 0, root);
KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = findProperty(props, clickedProperty);
if (prop->isInStasis) {
return true;
}
}
return false;
}
void NodeDelegate::Private::getParentsIndex(QList &items, const QModelIndex &index)
{
if (!index.isValid()) return;
items.append(index);
getParentsIndex(items, index.parent());
}
void NodeDelegate::Private::getChildrenIndex(QList &items, const QModelIndex &index)
{
qint32 childs = view->model()->rowCount(index);
QModelIndex child;
// STEP 1: Go.
for (quint16 i = 0; i < childs; ++i) {
child = view->model()->index(i, 0, index);
items.append(child);
getChildrenIndex(items, child);
}
}
void NodeDelegate::Private::getSiblingsIndex(QList &items, const QModelIndex &index)
{
qint32 numberOfLeaves = view->model()->rowCount(index.parent());
QModelIndex item;
// STEP 1: Go.
for (quint16 i = 0; i < numberOfLeaves; ++i) {
item = view->model()->index(i, 0, index.parent());
if (item != index) {
items.append(item);
}
}
}
void NodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect rc = iconsRect(option, index);
QTransform oldTransform = p->transform();
QPen oldPen = p->pen();
p->setTransform(QTransform::fromTranslate(rc.x(), rc.y()));
p->setPen(scm.gridColor(option, d->view));
int x = 0;
const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
QList realProps = d->rightmostProperties(props);
if (option.direction == Qt::RightToLeft) {
std::reverse(realProps.begin(), realProps.end());
}
Q_FOREACH (OptionalProperty prop, realProps) {
if (option.direction == Qt::LeftToRight)
p->drawLine(x, 0, x, scm.rowHeight() - scm.border());
x += scm.iconMargin();
if (prop) {
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled;
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (fullColor) {
p->setOpacity(1.0);
}
else {
p->setOpacity(0.35);
}
p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal));
p->setOpacity(oldOpacity); // restore old opacity
}
x += scm.iconSize() + scm.iconMargin();
if (!(option.direction == Qt::LeftToRight))
p->drawLine(x, 0, x, scm.rowHeight() - scm.border());
x += scm.border();
}
p->setTransform(oldTransform);
p->setPen(oldPen);
}
QRect NodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relVisibilityRect();
rc.setHeight(scm.rowHeight());
// Move to current index
rc.moveCenter(option.rect.center());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
// HACK: Without the -5, the right edge is outside the view
rc.moveRight(d->view->width()-5);
} else {
rc.moveLeft(0);
}
return rc;
}
QRect NodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relDecorationRect();
// Move to current index
rc.moveTop(option.rect.topLeft().y());
rc.setHeight(scm.rowHeight());
// Move to correct location.
if (option.direction == Qt::RightToLeft) {
rc.moveRight(option.rect.topRight().x());
} else {
rc.moveLeft(option.rect.topLeft().x());
}
return rc;
}
void NodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
/**
* Small hack Alert:
*
* Here wepaint over the area that sits basically outside our layer's
* row. Anyway, just update it later...
*/
KisNodeViewColorScheme scm;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
OptionalProperty prop = d->findVisibilityProperty(props);
if (!prop) return;
QRect fitRect = visibilityClickRect(option, index);
// Shrink to icon rect
fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin()+scm.border()));
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
// if we are not showing the layer, make the icon slightly transparent like other inactive icons
const qreal oldOpacity = p->opacity();
if (!prop->state.toBool()) {
p->setOpacity(0.35);
}
QPixmap pixmapIcon(icon.pixmap(scm.visibilitySize(), QIcon::Active));
p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
if (prop->isInStasis) {
QPainter::CompositionMode prevComposition = p->compositionMode();
p->setCompositionMode(QPainter::CompositionMode_HardLight);
pixmapIcon = icon.pixmap(scm.visibilitySize(), QIcon::Active);
QBitmap mask = pixmapIcon.mask();
pixmapIcon.fill(d->view->palette().color(QPalette::Highlight));
pixmapIcon.setMask(mask);
p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
p->setCompositionMode(prevComposition);
}
p->setOpacity(oldOpacity);
//// For debugging purposes only
// // // p->save();
// // // p->setPen(Qt::blue);
// // // KritaUtils::renderExactRect(p, visibilityClickRect(option, index));
// // // p->restore();
}
void NodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QIcon icon = index.data(Qt::DecorationRole).value();
if (!icon.isNull()) {
QPixmap pixmap = icon.pixmap(scm.decorationSize(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
QRect rc = decorationClickRect(option, index);
// Shrink to icon rect
rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap);
p->setOpacity(oldOpacity); // restore old opacity
}
}
void NodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = decorationClickRect(option, index);
// Move to current index
// rc.moveTop(option.rect.topLeft().y());
// Shrink to icon rect
rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
if (!(option.state & QStyle::State_Children)) return;
QString iconName = option.state & QStyle::State_Open ?
"arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right");
QIcon icon = KisIconUtils::loadIcon(iconName);
QPixmap pixmap = icon.pixmap(rc.width(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap);
}
bool NodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
KisNodeViewColorScheme scm;
QStyleOptionViewItem newOption = option;
newOption.rect = d->view->originalVisualRect(index);
if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
&& (index.flags() & Qt::ItemIsEnabled))
{
QMouseEvent *mouseEvent = static_cast(event);
/**
* Small hack Alert:
*
* Here we handle clicking even when it happened outside
* the rectangle of the current index. The point is, we
* use some virtual scroling offset to move the tree to the
* right of the visibility icon. So the icon itself is placed
* in an empty area that doesn't belong to any index. But we still
* handle it.
*/
const QRect visibilityRect = visibilityClickRect(newOption, index);
const bool visibilityClicked = visibilityRect.isValid() &&
visibilityRect.contains(mouseEvent->pos());
const QRect thumbnailRect = thumbnailClickRect(newOption, index);
const bool thumbnailClicked = thumbnailRect.isValid() &&
thumbnailRect.contains(mouseEvent->pos());
const QRect decorationRect = decorationClickRect(newOption, index);
const bool decorationClicked = decorationRect.isValid() &&
decorationRect.contains(mouseEvent->pos());
const QRect iconsRect = this->iconsRect(newOption, index);
const bool iconsClicked = iconsRect.isValid() &&
iconsRect.contains(mouseEvent->pos());
const bool leftButton = mouseEvent->buttons() & Qt::LeftButton;
if (leftButton && iconsClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
QList realProps = d->rightmostProperties(props);
if (newOption.direction == Qt::RightToLeft) {
std::reverse(realProps.begin(), realProps.end());
}
const int numProps = realProps.size();
const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border();
const int xPos = mouseEvent->pos().x() - iconsRect.left();
const int clickedIcon = xPos / iconWidth;
const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth);
if (iconsClicked &&
clickedIcon >= 0 &&
clickedIcon < numProps &&
distToBorder > scm.iconMargin()) {
OptionalProperty clickedProperty = realProps[clickedIcon];
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index);
return true;
}
} else if (leftButton && visibilityClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
OptionalProperty clickedProperty = d->findVisibilityProperty(props);
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index);
return true;
} else if (leftButton && decorationClicked) {
bool isExpandable = model->hasChildren(index);
if (isExpandable) {
bool isExpanded = d->view->isExpanded(index);
d->view->setExpanded(index, !isExpanded);
}
return true;
} else if (leftButton && thumbnailClicked) {
bool hasCorrectModifier = false;
SelectionAction action = SELECTION_REPLACE;
if (mouseEvent->modifiers() == Qt::ControlModifier) {
action = SELECTION_REPLACE;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
action = SELECTION_ADD;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) {
action = SELECTION_SUBTRACT;
hasCorrectModifier = true;
} else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
action = SELECTION_INTERSECT;
hasCorrectModifier = true;
}
if (hasCorrectModifier) {
model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole);
}
d->view->setCurrentIndex(index);
return hasCorrectModifier; //If not here then the item is !expanded when reaching return false;
}
if (mouseEvent->button() == Qt::LeftButton &&
mouseEvent->modifiers() == Qt::AltModifier) {
d->view->setCurrentIndex(index);
model->setData(index, true, KisNodeModel::AlternateActiveRole);
return true;
}
}
else if (event->type() == QEvent::ToolTip) {
if (!KisConfig(true).hidePopups()) {
QHelpEvent *helpEvent = static_cast(event);
d->tip.showTip(d->view, helpEvent->pos(), newOption, index);
}
return true;
} else if (event->type() == QEvent::Leave) {
d->tip.hide();
}
return false;
}
QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const
{
// #400357 do not override QAbstractItemDelegate::setEditorData to update editor's text
// because replacing the text while user type is confusing
const QString &text = index.data(Qt::DisplayRole).toString();
d->edit = new QLineEdit(text, parent);
d->edit->setFocusPolicy(Qt::StrongFocus);
d->edit->installEventFilter(const_cast(this)); //hack?
return d->edit;
}
void NodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast(widget);
Q_ASSERT(edit);
model->setData(index, edit->text(), Qt::DisplayRole);
}
void NodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
widget->setGeometry(option.rect);
}
+void NodeDelegate::toggleSolo(const QModelIndex &index) {
+ KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value();
+ OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
+ d->toggleProperty(props, visibilityProperty, Qt::ShiftModifier, index);
+}
+
// PROTECTED
bool NodeDelegate::eventFilter(QObject *object, QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress: {
if (d->edit) {
QMouseEvent *me = static_cast(event);
if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) {
emit commitData(d->edit);
emit closeEditor(d->edit);
}
}
} break;
case QEvent::KeyPress: {
QLineEdit *edit = qobject_cast(object);
if (edit && edit == d->edit) {
QKeyEvent *ke = static_cast(event);
switch (ke->key()) {
case Qt::Key_Escape:
emit closeEditor(edit);
return true;
case Qt::Key_Tab:
emit commitData(edit);
emit closeEditor(edit, EditNextItem);
return true;
case Qt::Key_Backtab:
emit commitData(edit);
emit closeEditor(edit, EditPreviousItem);
return true;
case Qt::Key_Return:
case Qt::Key_Enter:
emit commitData(edit);
emit closeEditor(edit);
return true;
default: break;
}
}
} break;
case QEvent::ShortcutOverride : {
QLineEdit *edit = qobject_cast(object);
if (edit && edit == d->edit){
auto* key = static_cast(event);
if (key->modifiers() == Qt::NoModifier){
switch (key->key()){
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
case Qt::Key_Return:
case Qt::Key_Enter:
event->accept();
return true;
default: break;
}
}
}
} break;
case QEvent::FocusOut : {
QLineEdit *edit = qobject_cast(object);
if (edit && edit == d->edit) {
emit commitData(edit);
emit closeEditor(edit);
}
}
default: break;
}
return QAbstractItemDelegate::eventFilter(object, event);
}
// PRIVATE
QStyleOptionViewItem NodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
{
QStyleOptionViewItem option = o;
QVariant v = index.data(Qt::FontRole);
if (v.isValid()) {
option.font = v.value();
option.fontMetrics = QFontMetrics(option.font);
}
v = index.data(Qt::TextAlignmentRole);
if (v.isValid())
option.displayAlignment = QFlag(v.toInt());
v = index.data(Qt::TextColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Text, v.value());
v = index.data(Qt::BackgroundColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Window, v.value());
return option;
}
void NodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant value = index.data(KisNodeModel::ProgressRole);
if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
/// The progress bar will display under the layer name area. The bars have accurate data, so we
/// probably don't need to also show the actual number for % complete
KisNodeViewColorScheme scm;
const QRect thumbnailRect = thumbnailClickRect(option, index);
const QRect iconsRectR = iconsRect(option, index);
const int height = 5;
const QRect rc = QRect(
((option.direction == Qt::RightToLeft) ?
iconsRectR.bottomRight() :
thumbnailRect.bottomRight()) - QPoint(0, height),
((option.direction == Qt::RightToLeft) ?
thumbnailRect.bottomLeft() :
iconsRectR.bottomLeft()));
p->save();
{
p->setClipRect(rc);
QStyle* style = QApplication::style();
QStyleOptionProgressBar opt;
opt.rect = rc;
opt.minimum = 0;
opt.maximum = 100;
opt.progress = value.toInt();
opt.textVisible = false;
opt.textAlignment = Qt::AlignHCenter;
opt.text = i18n("%1 %", opt.progress);
opt.orientation = Qt::Horizontal;
opt.state = option.state;
style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
}
p->restore();
}
}
void NodeDelegate::slotConfigChanged()
{
KisConfig cfg(true);
d->checkersColor1 = cfg.checkersColor1();
d->checkersColor2 = cfg.checkersColor2();
}
void NodeDelegate::slotUpdateIcon()
{
KisLayerPropertiesIcons::instance()->updateIcons();
}
void NodeDelegate::slotResetState(){
NodeView *view = d->view;
QModelIndex root = view->rootIndex();
int childs = view->model()->rowCount(root);
if (childs > 0){
QModelIndex firstChild = view->model()->index(0, 0, root);
KisBaseNode::PropertyList props = firstChild.data(KisNodeModel::PropertiesRole).value();
OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
if(d->stasisIsDirty(root, visibilityProperty)){ // clean inStasis if mixed!
d->resetPropertyStateRecursive(root, visibilityProperty);
}
}
}
diff --git a/plugins/dockers/layerdocker/NodeDelegate.h b/plugins/dockers/layerdocker/NodeDelegate.h
index 65771ad04a..1c6393a1da 100644
--- a/plugins/dockers/layerdocker/NodeDelegate.h
+++ b/plugins/dockers/layerdocker/NodeDelegate.h
@@ -1,92 +1,94 @@
/*
Copyright (c) 2006 Gábor Lehel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DOCUMENT_SECTION_DELEGATE_H
#define KIS_DOCUMENT_SECTION_DELEGATE_H
#include
class NodeView;
class KisNodeModel;
/**
* See KisNodeModel and NodeView.
*
* A delegate provides the gui machinery, using Qt's model/view terminology.
* This class is owned by NodeView to do the work of generating the
* graphical representation of each item.
*/
class NodeDelegate: public QAbstractItemDelegate
{
Q_OBJECT
public:
explicit NodeDelegate(NodeView *view, QObject *parent = 0);
~NodeDelegate() override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const override;
+ void toggleSolo(const QModelIndex &index);
+
void slotUpdateIcon();
Q_SIGNALS:
void resetVisibilityStasis();
protected:
bool eventFilter(QObject *object, QEvent *event) override;
private:
typedef KisNodeModel Model;
typedef NodeView View;
class Private;
Private* const d;
static QStyleOptionViewItem getOptions(const QStyleOptionViewItem &option, const QModelIndex &index);
void drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const;
private Q_SLOTS:
void slotConfigChanged();
void slotResetState();
};
#endif
diff --git a/plugins/dockers/layerdocker/NodeView.cpp b/plugins/dockers/layerdocker/NodeView.cpp
index 057a9676af..29e0818b15 100644
--- a/plugins/dockers/layerdocker/NodeView.cpp
+++ b/plugins/dockers/layerdocker/NodeView.cpp
@@ -1,592 +1,596 @@
/*
Copyright (c) 2006 Gábor Lehel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "NodeView.h"
#include "NodePropertyAction_p.h"
#include "NodeDelegate.h"
#include "NodeViewVisibilityDelegate.h"
#include "kis_node_model.h"
#include "kis_signals_blocker.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_node_view_color_scheme.h"
#ifdef HAVE_X11
#define DRAG_WHILE_DRAG_WORKAROUND
#endif
#ifdef DRAG_WHILE_DRAG_WORKAROUND
#define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true
#define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false
#else
#define DRAG_WHILE_DRAG_WORKAROUND_START()
#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
#endif
class Q_DECL_HIDDEN NodeView::Private
{
public:
Private(NodeView* _q)
: delegate(_q, _q)
, mode(DetailedMode)
#ifdef DRAG_WHILE_DRAG_WORKAROUND
, isDragging(false)
#endif
{
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group("NodeView");
mode = (DisplayMode) group.readEntry("NodeViewMode", (int)MinimalMode);
}
NodeDelegate delegate;
DisplayMode mode;
QPersistentModelIndex hovered;
QPoint lastPos;
#ifdef DRAG_WHILE_DRAG_WORKAROUND
bool isDragging;
#endif
};
NodeView::NodeView(QWidget *parent)
: QTreeView(parent)
, m_draggingFlag(false)
, d(new Private(this))
{
setItemDelegateForColumn(0, &d->delegate);
setMouseTracking(true);
setSelectionBehavior(SelectRows);
setDefaultDropAction(Qt::MoveAction);
setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
setSelectionMode(QAbstractItemView::ExtendedSelection);
header()->hide();
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragDrop);
setAcceptDrops(true);
setDropIndicatorShown(true);
{
QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
if (scroller) {
connect(scroller, SIGNAL(stateChanged(QScroller::State)),
this, SLOT(slotScrollerStateChanged(QScroller::State)));
}
}
}
NodeView::~NodeView()
{
delete d;
}
void NodeView::setDisplayMode(DisplayMode mode)
{
if (d->mode != mode) {
d->mode = mode;
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup group = config->group("NodeView");
group.writeEntry("NodeViewMode", (int)mode);
scheduleDelayedItemsLayout();
}
}
NodeView::DisplayMode NodeView::displayMode() const
{
return d->mode;
}
void NodeView::addPropertyActions(QMenu *menu, const QModelIndex &index)
{
KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value();
for (int i = 0, n = list.count(); i < n; ++i) {
if (list.at(i).isMutable) {
PropertyAction *a = new PropertyAction(i, list.at(i), index, menu);
connect(a, SIGNAL(toggled(bool,QPersistentModelIndex,int)),
this, SLOT(slotActionToggled(bool,QPersistentModelIndex,int)));
menu->addAction(a);
}
}
}
void NodeView::updateNode(const QModelIndex &index)
{
dataChanged(index, index);
}
+void NodeView::toggleSolo(const QModelIndex &index) {
+ d->delegate.toggleSolo(index);
+}
+
QItemSelectionModel::SelectionFlags NodeView::selectionCommand(const QModelIndex &index,
const QEvent *event) const
{
/**
* Qt has a bug: when we Ctrl+click on an item, the item's
* selections gets toggled on mouse *press*, whereas usually it is
* done on mouse *release*. Therefore the user cannot do a
* Ctrl+D&D with the default configuration. This code fixes the
* problem by manually returning QItemSelectionModel::NoUpdate
* flag when the user clicks on an item and returning
* QItemSelectionModel::Toggle on release.
*/
if (event &&
(event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) &&
index.isValid()) {
const QMouseEvent *mevent = static_cast(event);
if (mevent->button() == Qt::RightButton &&
selectionModel()->selectedIndexes().contains(index)) {
// Allow calling context menu for multiple layers
return QItemSelectionModel::NoUpdate;
}
if (event->type() == QEvent::MouseButtonPress &&
(mevent->modifiers() & Qt::ControlModifier)) {
return QItemSelectionModel::NoUpdate;
}
if (event->type() == QEvent::MouseButtonRelease &&
(mevent->modifiers() & Qt::ControlModifier)) {
return QItemSelectionModel::Toggle;
}
}
/**
* Qt 5.6 has a bug: it reads global modifiers, not the ones
* passed from event. So if you paste an item using Ctrl+V it'll
* select multiple layers for you
*/
Qt::KeyboardModifiers globalModifiers = QApplication::keyboardModifiers();
if (!event && globalModifiers != Qt::NoModifier) {
return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
}
return QAbstractItemView::selectionCommand(index, event);
}
QRect NodeView::visualRect(const QModelIndex &index) const
{
QRect rc = QTreeView::visualRect(index);
if (layoutDirection() == Qt::RightToLeft)
rc.setRight(width());
else
rc.setLeft(0);
return rc;
}
QRect NodeView::originalVisualRect(const QModelIndex &index) const
{
return QTreeView::visualRect(index);
}
QModelIndex NodeView::indexAt(const QPoint &point) const
{
KisNodeViewColorScheme scm;
QModelIndex index = QTreeView::indexAt(point);
if (!index.isValid()) {
// Middle is a good position for both LTR and RTL layouts
// First reset x, then get the x in the middle
index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0));
}
return index;
}
bool NodeView::viewportEvent(QEvent *e)
{
if (model()) {
switch(e->type()) {
case QEvent::MouseButtonPress: {
DRAG_WHILE_DRAG_WORKAROUND_STOP();
const QPoint pos = static_cast(e)->pos();
d->lastPos = pos;
if (!indexAt(pos).isValid()) {
return QTreeView::viewportEvent(e);
}
QModelIndex index = model()->buddy(indexAt(pos));
if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
return true;
}
} break;
case QEvent::Leave: {
QEvent e(QEvent::Leave);
d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
d->hovered = QModelIndex();
} break;
case QEvent::MouseMove: {
#ifdef DRAG_WHILE_DRAG_WORKAROUND
if (d->isDragging) {
return false;
}
#endif
const QPoint pos = static_cast(e)->pos();
QModelIndex hovered = indexAt(pos);
if (hovered != d->hovered) {
if (d->hovered.isValid()) {
QEvent e(QEvent::Leave);
d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
}
if (hovered.isValid()) {
QEvent e(QEvent::Enter);
d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered);
}
d->hovered = hovered;
}
/* This is a workaround for a bug in QTreeView that immediately begins a dragging action
when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */
Qt::MouseButtons buttons = static_cast(e)->buttons();
if ((Qt::LeftButton | Qt::MidButton) & buttons) {
if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) {
return QTreeView::viewportEvent(e);
}
return true;
}
} break;
case QEvent::ToolTip: {
const QPoint pos = static_cast(e)->pos();
if (!indexAt(pos).isValid()) {
return QTreeView::viewportEvent(e);
}
QModelIndex index = model()->buddy(indexAt(pos));
return d->delegate.editorEvent(e, model(), optionForIndex(index), index);
} break;
case QEvent::Resize: {
scheduleDelayedItemsLayout();
break;
}
default: break;
}
}
return QTreeView::viewportEvent(e);
}
void NodeView::contextMenuEvent(QContextMenuEvent *e)
{
QTreeView::contextMenuEvent(e);
QModelIndex i = indexAt(e->pos());
if (model())
i = model()->buddy(i);
showContextMenu(e->globalPos(), i);
}
void NodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index)
{
emit contextMenuRequested(globalPos, index);
}
void NodeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
if (current != previous) {
Q_ASSERT(!current.isValid() || current.model() == model());
model()->setData(current, true, KisNodeModel::ActiveRole);
}
}
void NodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &/*roles*/)
{
QTreeView::dataChanged(topLeft, bottomRight);
for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
QModelIndex index = topLeft.sibling(x, y);
if (index.data(KisNodeModel::ActiveRole).toBool()) {
if (currentIndex() != index) {
setCurrentIndex(index);
}
return;
}
}
}
}
void NodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
QTreeView::selectionChanged(selected, deselected);
emit selectionChanged(selectedIndexes());
}
void NodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
{
KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value();
list[num].state = on;
const_cast(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole);
}
QStyleOptionViewItem NodeView::optionForIndex(const QModelIndex &index) const
{
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(index);
if (index == currentIndex())
option.state |= QStyle::State_HasFocus;
return option;
}
void NodeView::startDrag(Qt::DropActions supportedActions)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
if (displayMode() == NodeView::ThumbnailMode) {
const QModelIndexList indexes = selectionModel()->selectedIndexes();
if (!indexes.isEmpty()) {
QMimeData *data = model()->mimeData(indexes);
if (!data) {
return;
}
QDrag *drag = new QDrag(this);
drag->setPixmap(createDragPixmap());
drag->setMimeData(data);
//m_dragSource = this;
drag->exec(supportedActions);
}
}
else {
QTreeView::startDrag(supportedActions);
}
}
QPixmap NodeView::createDragPixmap() const
{
const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
Q_ASSERT(!selectedIndexes.isEmpty());
const int itemCount = selectedIndexes.count();
// If more than one item is dragged, align the items inside a
// rectangular grid. The maximum grid size is limited to 4 x 4 items.
int xCount = 2;
int size = 96;
if (itemCount > 9) {
xCount = 4;
size = KisIconUtils::SizeLarge;
}
else if (itemCount > 4) {
xCount = 3;
size = KisIconUtils::SizeHuge;
}
else if (itemCount < xCount) {
xCount = itemCount;
}
int yCount = itemCount / xCount;
if (itemCount % xCount != 0) {
++yCount;
}
if (yCount > xCount) {
yCount = xCount;
}
// Draw the selected items into the grid cells
QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1);
dragPixmap.fill(Qt::transparent);
QPainter painter(&dragPixmap);
int x = 0;
int y = 0;
Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) {
const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value();
painter.drawPixmap(x, y, QPixmap().fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)));
x += size + 1;
if (x >= dragPixmap.width()) {
x = 0;
y += size + 1;
}
if (y >= dragPixmap.height()) {
break;
}
}
return dragPixmap;
}
void NodeView::resizeEvent(QResizeEvent * event)
{
KisNodeViewColorScheme scm;
header()->setStretchLastSection(false);
header()->setOffset(-scm.visibilityColumnWidth());
header()->resizeSection(0, event->size().width() - scm.visibilityColumnWidth());
setIndentation(scm.indentation());
QTreeView::resizeEvent(event);
}
void NodeView::paintEvent(QPaintEvent *event)
{
event->accept();
QTreeView::paintEvent(event);
// Paint the line where the slide should go
if (isDragging() && (displayMode() == NodeView::ThumbnailMode)) {
QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
int numberRow = cursorPageIndex();
int scrollBarValue = verticalScrollBar()->value();
QPoint point1(0, numberRow * size.height() - scrollBarValue);
QPoint point2(size.width(), numberRow * size.height() - scrollBarValue);
QLineF line(point1, point2);
QPainter painter(this->viewport());
QPen pen = QPen(palette().brush(QPalette::Highlight), 8);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.setOpacity(0.8);
painter.drawLine(line);
}
}
void NodeView::drawBranches(QPainter *painter, const QRect &rect,
const QModelIndex &index) const
{
Q_UNUSED(painter);
Q_UNUSED(rect);
Q_UNUSED(index);
/**
* Noop... Everything is going to be painted by NodeDelegate.
* So this override basically disables painting of Qt's branch-lines.
*/
}
void NodeView::dropEvent(QDropEvent *ev)
{
if (displayMode() == NodeView::ThumbnailMode) {
setDraggingFlag(false);
ev->accept();
clearSelection();
if (!model()) {
return;
}
int newIndex = cursorPageIndex();
model()->dropMimeData(ev->mimeData(), ev->dropAction(), newIndex, -1, QModelIndex());
return;
}
QTreeView::dropEvent(ev);
DRAG_WHILE_DRAG_WORKAROUND_STOP();
}
int NodeView::cursorPageIndex() const
{
QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
int scrollBarValue = verticalScrollBar()->value();
QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos());
int numberRow = (cursorPosition.y() + scrollBarValue) / size.height();
//If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is
//performed before the page
if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) {
numberRow++;
}
if (numberRow > model()->rowCount(QModelIndex())) {
numberRow = model()->rowCount(QModelIndex());
}
return numberRow;
}
void NodeView::dragEnterEvent(QDragEnterEvent *ev)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
QVariant data = QVariant::fromValue(
static_cast(const_cast(ev->mimeData())));
model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled);
QTreeView::dragEnterEvent(ev);
}
void NodeView::dragMoveEvent(QDragMoveEvent *ev)
{
DRAG_WHILE_DRAG_WORKAROUND_START();
if (displayMode() == NodeView::ThumbnailMode) {
ev->accept();
if (!model()) {
return;
}
QTreeView::dragMoveEvent(ev);
setDraggingFlag();
viewport()->update();
return;
}
QTreeView::dragMoveEvent(ev);
}
void NodeView::dragLeaveEvent(QDragLeaveEvent *e)
{
if (displayMode() == NodeView::ThumbnailMode) {
setDraggingFlag(false);
} else {
QTreeView::dragLeaveEvent(e);
}
DRAG_WHILE_DRAG_WORKAROUND_STOP();
}
bool NodeView::isDragging() const
{
return m_draggingFlag;
}
void NodeView::setDraggingFlag(bool flag)
{
m_draggingFlag = flag;
}
void NodeView::slotUpdateIcons()
{
d->delegate.slotUpdateIcon();
}
void NodeView::slotScrollerStateChanged(QScroller::State state){
KisKineticScroller::updateCursor(this, state);
}
diff --git a/plugins/dockers/layerdocker/NodeView.h b/plugins/dockers/layerdocker/NodeView.h
index 489c2c1c85..9a2c5e9300 100644
--- a/plugins/dockers/layerdocker/NodeView.h
+++ b/plugins/dockers/layerdocker/NodeView.h
@@ -1,187 +1,189 @@
/*
Copyright (c) 2006 Gábor Lehel
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_DOCUMENT_SECTION_VIEW_H
#define KIS_DOCUMENT_SECTION_VIEW_H
#include
#include
class QStyleOptionViewItem;
class KisNodeModel;
/**
* A widget displaying the Krita nodes (layers, masks, local selections, etc.)
*
* The widget can show the document sections as big thumbnails,
* in a listview with two rows of informative text and icons,
* or as single rows of text and property icons.
*
* This class is designed as a Qt model-view widget.
*
* The Qt documentation explains the design and terminology for these classes:
* https://doc.qt.io/qt-5/model-view-programming.html
*
* This widget should work correctly in your Qt designer .ui file.
*/
class NodeView: public QTreeView
{
Q_OBJECT
Q_SIGNALS:
/**
* Emitted whenever the user clicks with the secondary mouse
* button on an item. It is up to the application to design the
* contents of the context menu and show it.
*/
void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index);
void selectionChanged(const QModelIndexList &);
public:
/**
* Create a new NodeView.
*/
explicit NodeView(QWidget *parent = 0);
~NodeView() override;
/// how items should be displayed
enum DisplayMode {
/// large fit-to-width thumbnails, with only titles or page numbers
ThumbnailMode,
/// smaller thumbnails, with titles and property icons in two rows
DetailedMode,
/// no thumbnails, with titles and property icons in a single row
MinimalMode
};
void resizeEvent(QResizeEvent * event) override;
void paintEvent (QPaintEvent *event) override;
void drawBranches(QPainter *painter, const QRect &rect,
const QModelIndex &index) const override;
void dropEvent(QDropEvent *ev) override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dragMoveEvent(QDragMoveEvent *ev) override;
void dragLeaveEvent(QDragLeaveEvent *e) override;
/**
* Set the display mode of the view to one of the options.
*
* @param mode The NodeView::DisplayMode mode
*/
void setDisplayMode(DisplayMode mode);
/**
* @return the currently active display mode
*/
DisplayMode displayMode() const;
/**
* Add toggle actions for all the properties associated with the
* current document section associated with the model index to the
* specified menu.
*
* For instance, if a document section can be locked and visible,
* the menu will be expanded with locked and visible toggle
* actions.
*
* For instance
@code
NodeView * nodeView;
QModelIndex index = getCurrentNode();
QMenu menu;
if (index.isValid()) {
sectionView->addPropertyActions(&menu, index);
} else {
menu.addAction(...); // Something to create a new document section, for example.
}
@endcode
*
* @param menu A pointer to the menu that will be expanded with
* the toglge actions
* @param index The model index associated with the document
* section that may or may not provide a number of toggle actions.
*/
void addPropertyActions(QMenu *menu, const QModelIndex &index);
void updateNode(const QModelIndex &index);
+ void toggleSolo(const QModelIndex &index);
+
QRect originalVisualRect(const QModelIndex &index) const;
protected:
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index,
const QEvent *event) const override;
QRect visualRect(const QModelIndex &index) const override;
QModelIndex indexAt(const QPoint &point) const override;
bool viewportEvent(QEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index);
void startDrag (Qt::DropActions supportedActions) override;
QPixmap createDragPixmap() const;
/**
* Calculates the index of the nearest item to the cursor position
*/
int cursorPageIndex() const;
public Q_SLOTS:
/// called with a theme change to refresh icon colors
void slotUpdateIcons();
void slotScrollerStateChanged(QScroller::State state);
protected Q_SLOTS:
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override;
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override;
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
private Q_SLOTS:
void slotActionToggled(bool on, const QPersistentModelIndex &index, int property);
private:
/**
* Permit to know if a slide is dragging
*
* @return boolean
*/
bool isDragging() const;
/**
* Setter for the dragging flag
*
* @param flag boolean
*/
void setDraggingFlag(bool flag = true);
bool m_draggingFlag;
QStyleOptionViewItem optionForIndex(const QModelIndex &index) const;
typedef KisNodeModel Model;
class PropertyAction;
class Private;
Private* const d;
};
#endif