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