diff --git a/Messages.sh b/Messages.sh index f4cd49d0ea..25aea70ad2 100755 --- a/Messages.sh +++ b/Messages.sh @@ -1,27 +1,27 @@ #! /bin/sh -source krita_xgettext.sh +source kundo2_aware_xgettext.sh $EXTRACTRC `find . -name \*.ui | grep -v '/tests/'` >> rc.cpp RCFILES=`find . -name \*.xmlgui \ | grep -v plugins/extensions/metadataeditor/editors/dublincore.xmlgui \ | grep -v plugins/extensions/metadataeditor/editors/exif.xmlgui \ | grep -v krita/sketch/KritaSketchWin.xmlgui \ | grep -v krita/gemini/KritaGeminiWin.xmlgui ` $EXTRACTRC $RCFILES >> rc.cpp ACTIONFILES=`find . -name \*.action` ./action_i18n.pl --context=action $ACTIONFILES >> rc.cpp # extracti18n.pl extracts additional data from brushes, palettes etc. perl extracti18n.pl > i18ndata # Ignore sdk/templates which contains templates for writing future plugins. # Also ignore crashreporter, it has it's own catalog # None of the placeholder strings inside will be seen by users. -krita_xgettext krita.pot i18ndata rc.cpp \ +kundo2_aware_xgettext krita.pot i18ndata rc.cpp \ `find . -name \*.cc -o -name \*.h -o -name \*.cpp | \ grep -v '/tests/' | grep -v './sdk/templates' | grep -v './krita/crashreporter/'` # Clean up rm -f i18ndata rc.cpp diff --git a/krita/data/actions/ConnectionTool.action b/krita/data/actions/ConnectionTool.action index 71d6d26af6..22e2b77af1 100644 --- a/krita/data/actions/ConnectionTool.action +++ b/krita/data/actions/ConnectionTool.action @@ -1,156 +1,160 @@ + diff --git a/krita/data/profiles/elles-icc-profiles/CMakeLists.txt b/krita/data/profiles/elles-icc-profiles/CMakeLists.txt index f5f1b3ffbc..1b3d466513 100644 --- a/krita/data/profiles/elles-icc-profiles/CMakeLists.txt +++ b/krita/data/profiles/elles-icc-profiles/CMakeLists.txt @@ -1,97 +1,28 @@ ########### install files ############### install(FILES - ACES-elle-V2-g10.icc - ACES-elle-V4-g10.icc - ACES-elle-V2-g22.icc - ACES-elle-V4-g22.icc - ACES-elle-V2-labl.icc - ACES-elle-V4-labl.icc - ACES-elle-V2-srgbtrc.icc - ACES-elle-V4-srgbtrc.icc - ACEScg-elle-V2-g10.icc ACEScg-elle-V4-g10.icc - ACEScg-elle-V2-labl.icc - ACEScg-elle-V4-labl.icc - ACEScg-elle-V2-srgbtrc.icc - ACEScg-elle-V4-srgbtrc.icc - AllColorsRGB-elle-V2-g10.icc - AllColorsRGB-elle-V4-g10.icc - AllColorsRGB-elle-V2-labl.icc - AllColorsRGB-elle-V4-labl.icc - AllColorsRGB-elle-V2-srgbtrc.icc - AllColorsRGB-elle-V4-srgbtrc.icc - CIERGB-elle-V2-g10.icc - CIERGB-elle-V2-g22.icc - CIERGB-elle-V2-srgbtrc.icc - CIERGB-elle-V4-g10.icc - CIERGB-elle-V4-g22.icc - CIERGB-elle-V4-srgbtrc.icc - CIERGB-elle-V2-labl.icc - CIERGB-elle-V4-labl.icc - ClayRGB-elle-V2-g10.icc - ClayRGB-elle-V2-g22.icc - ClayRGB-elle-V2-srgbtrc.icc - ClayRGB-elle-V4-g10.icc - ClayRGB-elle-V4-g22.icc ClayRGB-elle-V4-srgbtrc.icc - ClayRGB-elle-V2-labl.icc; - ClayRGB-elle-V4-labl.icc - CMakeLists.txt + Gray-D50-elle-V2-g10.icc Gray-D50-elle-V2-g18.icc Gray-D50-elle-V2-g22.icc Gray-D50-elle-V2-srgbtrc.icc Gray-D50-elle-V4-g10.icc Gray-D50-elle-V4-g18.icc Gray-D50-elle-V4-g22.icc Gray-D50-elle-V4-srgbtrc.icc Gray-D50-elle-V2-labl.icc Gray-D50-elle-V4-labl.icc Gray-D50-elle-V2-rec709.icc Gray-D50-elle-V4-rec709.icc - IdentityRGB-elle-V2-g10.icc - IdentityRGB-elle-V2-srgbtrc.icc - IdentityRGB-elle-V4-g10.icc - IdentityRGB-elle-V4-srgbtrc.icc - IdentityRGB-elle-V2-labl.icc - IdentityRGB-elle-V4-labl.icc + Lab-D50-Identity-elle-V2.icc Lab-D50-Identity-elle-V4.icc - LargeRGB-elle-V2-g10.icc - LargeRGB-elle-V2-g18.icc - LargeRGB-elle-V2-srgbtrc.icc - LargeRGB-elle-V4-g10.icc - LargeRGB-elle-V4-g18.icc - LargeRGB-elle-V4-srgbtrc.icc - LargeRGB-elle-V2-labl.icc - LargeRGB-elle-V4-labl.icc - Rec2020-elle-V2-rec709.icc - Rec2020-elle-V4-rec709.icc - Rec2020-elle-V2-g10.icc - Rec2020-elle-V4-g10.icc - Rec2020-elle-V2-labl.icc - Rec2020-elle-V4-labl.icc - Rec2020-elle-V2-srgbtrc.icc - Rec2020-elle-V4-srgbtrc.icc + sRGB-elle-V2-g10.icc sRGB-elle-V2-srgbtrc.icc - sRGB-elle-V4-g10.icc - sRGB-elle-V4-srgbtrc.icc - sRGB-elle-V2-labl.icc - sRGB-elle-V4-labl.icc - #sRGB-elle-V2-rec709.icc - #sRGB-elle-V4-rec709.icc - WideRGB-elle-V2-g10.icc - WideRGB-elle-V2-g22.icc - WideRGB-elle-V2-srgbtrc.icc - WideRGB-elle-V4-g10.icc - WideRGB-elle-V4-g22.icc - WideRGB-elle-V4-srgbtrc.icc - WideRGB-elle-V2-srgbtrc.icc - WideRGB-elle-V4-srgbtrc.icc - WideRGB-elle-V2-labl.icc - WideRGB-elle-V4-labl.icc + Lab-D50-Identity-elle-V2.icc Lab-D50-Identity-elle-V4.icc XYZ-D50-Identity-elle-V4.icc DESTINATION ${SHARE_INSTALL_PREFIX}/color/icc/krita) diff --git a/krita/krita.action b/krita/krita.action index 3f6759344a..c4038c13bb 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,2973 +1,2936 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false - - - Import Resources or Bundles... - - Import Resources or Bundles - Import Resources or Bundles - 0 - 0 - - false - - Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false Rename Composition... Rename Composition Rename Composition 0 0 false + + + Update Composition + + Update Composition + Update Composition + 0 + 0 + + false + + Painting Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false Increase opacity Increase opacity Increase opacity 0 0 O false Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 10000 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 10000 true Paste at cursor Paste at cursor Paste at cursor 0 0 false &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false 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 - - zoom-in - Zoom &In - - Zoom In - Zoom In - Ctrl++; Ctrl+= - false - - - - zoom-out - Zoom &Out - - Zoom Out - Zoom Out - Ctrl+- - false - - 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 Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 0 0 true Filters &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance.. Color Balance.. Color Balance.. 10000 0 Ctrl+B false - &PhongBumpmap... + &Phong Bumpmap... - PhongBumpmap - PhongBumpmap + 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 Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false References References References false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false Shape Manipulation Tool Shape Manipulation Tool Shape Manipulation Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick ends the curve. Bezier Curve Tool. Shift-mouseclick ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Ruler assistant editor tool Ruler assistant editor tool Ruler assistant editor tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false - - - Brush Selection Tool - - Brush Selection Tool - Brush Selection 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 Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false Add blank frame Add blank frame Add blank frame 100000 0 false Copy Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Add blank frame Add blank frame Add blank frame 100000 0 false Show in Timeline true Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true 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 0 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 0 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 100000000000000 0 false Quick Group Create a group layer containing selected layers Quick Group - 0 + 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup - 0 + 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group - 0 + 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 0 0 false Visible Layers Select all visible layers Select all visible layers 0 0 false Locked Layers Select all locked layers Select all locked layers 0 0 false Invisible Layers Select all invisible layers Select all invisible layers 0 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 0 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 100000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 100000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 100000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 100000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 100000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false &Merge 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 - - - &Merge Selected Layers - - Merge Selected Layers - Merge Selected Layers - Ctrl+Alt+E - false - - La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/krita.xmlgui b/krita/krita.xmlgui index 534e356436..cb21d45449 100644 --- a/krita/krita.xmlgui +++ b/krita/krita.xmlgui @@ -1,375 +1,375 @@ &File &Edit &View &Canvas &Snap To &Image &Rotate &Layer New &Import/Export Import &Convert &Select &Group &Transform &Rotate S&plit S&plit Alpha &Select Filte&r &Tools Recording Macros Setti&ngs &Help File Brushes and Stuff diff --git a/krita/kritamenu.action b/krita/kritamenu.action index 1e2b17b9d6..48befac8f4 100644 --- a/krita/kritamenu.action +++ b/krita/kritamenu.action @@ -1,1718 +1,1720 @@ File document-new &New Create new document New 0 0 Ctrl+N false document-open &Open... Open an existing document Open 0 0 Ctrl+O false document-open-recent Open &Recent Open a document which was recently opened Open Recent 1 0 false document-save &Save Save Save 1 0 Ctrl+S false document-save-as Save &As... Save document under a new name Save As 1 0 Ctrl+Shift+S false + document-import Open ex&isting Document as Untitled Document... Open existing Document as Untitled Document Open existing Document as Untitled Document 0 0 false document-export E&xport... Export Export 1 0 false application-pdf &Export as PDF... Export as PDF Export as PDF 1 0 false Import animation frames... Import animation frames Import animation frames 1 0 false &Render Animation... Render Animation to GIF, Image Sequence or Video Render Animation 1000 0 false &Render Image Sequence Again Render Animation to Image Sequence Again Render Animation 1000 0 false Save Incremental &Version Save Incremental Version Save Incremental Version 1 0 Ctrl+Alt+S false Save Incremental &Backup Save Incremental Backup Save Incremental Backup 1 0 F4 false &Create Template From Image... Create Template From Image Create Template From Image 1 0 false Create Copy &From Current Image Create Copy From Current Image Create Copy From Current Image 1 0 false document-print &Print... Print document Print 1 0 Ctrl+P false document-print-preview Print Previe&w Show a print preview of document Print Preview 1 0 false configure &Document Information Document Information Document Information 1 0 false &Close All Close All Close All 1 0 Ctrl+Shift+W false C&lose Close Close 1 0 false &Quit Quit application Quit 0 0 Ctrl+Q false Edit edit-undo Undo Undo last action Undo 1 0 Ctrl+Z false _edit-redo Redo Redo last undone action Redo 1 0 Ctrl+Shift+Z false edit-cut Cu&t Cut selection to clipboard Cut 0 0 Ctrl+X false edit-copy &Copy Copy selection to clipboard Copy 0 0 Ctrl+C false C&opy (sharp) Copy (sharp) Copy (sharp) 100000000 0 false Cut (&sharp) Cut (sharp) Cut (sharp) 100000000 0 false Copy &merged Copy merged Copy merged 100000000 0 Ctrl+Shift+C false edit-paste &Paste Paste clipboard content Paste 0 0 Ctrl+V false Paste into &New Image Paste into New Image Paste into New Image 0 0 Ctrl+Shift+N false edit-clear C&lear Clear Clear 1 0 Del false &Fill with Foreground Color Fill with Foreground Color Fill with Foreground Color 10000 1 Shift+Backspace false Fill &with Background Color Fill with Background Color Fill with Background Color 10000 1 Backspace false F&ill with Pattern Fill with Pattern Fill with Pattern 10000 1 false Stro&ke selected shapes Stroke selected shapes Stroke selected shapes 1000000000 0 false - Stroke selec&tion + Stroke Selec&tion... Stroke selection Stroke selection 10000000000 0 false Delete keyframe Delete keyframe Delete keyframe 100000 0 false Window window-new &New Window New Window New Window 0 0 false N&ext Next Next 10 0 false Previous Previous Previous false View &Show Canvas Only Show just the canvas or the whole window Show Canvas Only 0 0 Tab true view-fullscreen F&ull Screen Mode Display the window in full screen Full Screen Mode 0 0 Ctrl+Shift+F false &Wrap Around Mode Wrap Around Mode Wrap Around Mode 1 0 W true &Instant Preview Mode Instant Preview Mode Instant Preview Mode 1 0 Shift+L true Soft Proofing Turns on Soft Proofing Turns on Soft Proofing Ctrl+Y true Out of Gamut Warnings Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on. Ctrl+Shift+Y true Mirror View Mirror View Mirror View M false &Reset zoom Reset zoom Reset zoom 1 0 Ctrl+0 false Zoom &In Zoom In Zoom In 0 0 Ctrl++ false Zoom &Out Zoom Out Zoom Out 0 0 Ctrl+- false Rotate &Canvas Right Rotate Canvas Right Rotate Canvas Right 1 0 Ctrl+] false Rotate Canvas &Left Rotate Canvas Left Rotate Canvas Left 1 0 Ctrl+[ false Reset Canvas Rotation Reset Canvas Rotation Reset Canvas Rotation 1 0 false Show &Rulers The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. <p>Uncheck this to hide the rulers.</p> Show Rulers Show Rulers 1 0 true Rulers Track Pointer The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown Rulers Track Pointer Rulers Track Pointer 1 0 true Show Guides Show or hide guides Show Guides 1 0 true Lock Guides Lock or unlock guides Lock Guides 1 0 true Snap to Guides Snap cursor to guides position Snap to Guides 1 0 true Show Status &Bar Show or hide the status bar Show Status Bar 0 0 true view-grid Show &Grid Show Grid Show Grid 1000 0 Ctrl+Shift+' true Snap To Grid Snap To Grid Snap To Grid 1000 Ctrl+Shift+; true Show Snap Options Popup Show Snap Options Popup Show Snap Options Popup 1000 Shift+s false Snap Orthogonal Snap Orthogonal Snap Orthogonal 1000 true Snap Node Snap Node Snap Node 1000 true Snap Extension Snap Extension Snap Extension 1000 true Snap Intersection Snap Intersection Snap Intersection 1000 true Snap Bounding Box Snap Bounding Box Snap Bounding Box 1000 true Snap Image Bounds Snap Image Bounds Snap Image Bounds 1000 true Snap Image Center Snap Image Center Snap Image Center 1000 true S&how Painting Assistants Show Painting Assistants Show Painting Assistants 1000 0 true Show &Assistant Previews Show Assistant Previews Show Assistant Previews 1000 0 true Image document-properties &Properties... Properties Properties 1000 0 false format-stroke-color &Image Background Color and Transparency... Change the background color of the image Image Background Color and Transparency 1000 0 false &Convert Image Color Space... Convert Image Color Space Convert Image Color Space 1000 0 false trim-to-image &Trim to Image Size Trim to Image Size Trim to Image Size 1 0 false Trim to Current &Layer Trim to Current Layer Trim to Current Layer 100000 0 false Trim to S&election Trim to Selection Trim to Selection 100000000 0 false &Rotate Image... Rotate Image Rotate Image 1000 0 false object-rotate-right Rotate &Image 90° to the Right Rotate Image 90° to the Right Rotate Image 90° to the Right 1000 0 false object-rotate-left Rotate Image &90° to the Left Rotate Image 90° to the Left Rotate Image 90° to the Left 1000 0 false Rotate Image &180° Rotate Image 180° Rotate Image 180° 1000 0 false &Shear Image... Shear Image Shear Image 1000 0 false symmetry-horizontal &Mirror Image Horizontally Mirror Image Horizontally Mirror Image Horizontally 1000 0 false symmetry-vertical Mirror Image &Vertically Mirror Image Vertically Mirror Image Vertically 1000 0 false Scale Image To &New Size... Scale Image To New Size Scale Image To New Size 1000 0 Ctrl+Alt+I false &Offset Image... Offset Image Offset Image 1000 0 false R&esize Canvas... Resize Canvas Resize Canvas 1000 0 Ctrl+Alt+C false Im&age Split Image Split Image Split 1000 0 false Separate Ima&ge... Separate Image Separate Image 1000 0 false Select edit-select-all Select &All Select All Select All 0 0 Ctrl+A false edit-select-all &Deselect Deselect Deselect 1100000000 0 Ctrl+Shift+A false &Reselect Reselect Reselect 0 0 Ctrl+Shift+D false &Invert Invert Invert 10000 0 Ctrl+I false &Convert to Vector Selection Convert to Vector Selection Convert to Vector Selection 10000000000 0 false Convert Shapes to &Vector Selection Convert Shapes to Vector Selection Convert Shapes to Vector Selection 1000000000 0 false &Feather Selection... Feather Selection Feather Selection 10000000000 100 Shift+F6 false Dis&play Selection Display Selection Display Selection 1000 0 Ctrl+H true Sca&le... Scale Scale 100000000 100 false S&elect from Color Range... Select from Color Range Select from Color Range 10000 100 false Select &Opaque Select Opaque Select Opaque 10000 100 false &Grow Selection... Grow Selection Grow Selection 10000000000 100 false S&hrink Selection... Shrink Selection Shrink Selection 10000000000 100 false &Border Selection... Border Selection Border Selection 10000000000 100 false S&mooth Smooth Smooth 10000000000 100 false Filter &Apply Filter Again Apply Filter Again Apply Filter Again 0 0 Ctrl+F false Adjust Adjust Adjust false Artistic Artistic Artistic false Blur Blur Blur false Colors Colors Colors false Edge Detection Edge Detection Edge Detection false Enhance Enhance Enhance false Emboss Emboss Emboss false Map Map Map false Other Other Other false G'MIC Apply G'Mic Action Apply G'Mic Action false Tools media-record &Start recording macro Start recording macro Start recording macro 1000 0 false media-playback-stop Stop &recording actions Stop recording actions Stop recording actions 1000 0 false media-playback-start &Open and play... Open and play Open and play 0 0 false document-edit Open &and edit... Open and edit Open and edit 0 0 false Settings configure &Configure Krita... Configure Krita Configure Krita 0 0 false &Manage Resources... Manage Resources Manage Resources 0 0 false preferences-desktop-locale Switch Application &Language... Switch Application Language Switch Application Language false &Show Dockers Show Dockers Show Dockers 0 0 true Sho&w Docker Titlebars Show Docker Titlebars Show Docker Titlebars 0 0 true configure Configure Tool&bars... Configure Toolbars Configure Toolbars 0 0 false Dockers Dockers Dockers false &Themes Themes Themes false im-user Active Author Profile Active Author Profile Active Author Profile configure-shortcuts Configure S&hortcuts... Configure Shortcuts Configure Shortcuts 0 0 false &Window Window Window false Help help-contents Krita &Handbook Krita Handbook Krita Handbook F1 false tools-report-bug &Report Bug... Report Bug Report Bug false calligrakrita &About Krita About Krita About Krita false kde About &KDE About KDE About KDE false Brushes and Stuff &Gradients Gradients Gradients false &Patterns Patterns Patterns false &Color Color Color false &Painter's Tools Painter's Tools Painter's Tools false Brush composite Brush composite Brush composite false Brush option slider 1 Brush option slider 1 Brush option slider 1 false Brush option slider 2 Brush option slider 2 Brush option slider 2 false Brush option slider 3 Brush option slider 3 Brush option slider 3 false Mirror Mirror Mirror false Workspaces Workspaces Workspaces false diff --git a/krita/pics/misc-dark/dark_pivot-point.svg b/krita/pics/misc-dark/dark_pivot-point.svg new file mode 100644 index 0000000000..bc0eccbb06 --- /dev/null +++ b/krita/pics/misc-dark/dark_pivot-point.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/krita/pics/misc-dark/misc-dark-icons.qrc b/krita/pics/misc-dark/misc-dark-icons.qrc index fe5d162a0d..9b1007a880 100644 --- a/krita/pics/misc-dark/misc-dark-icons.qrc +++ b/krita/pics/misc-dark/misc-dark-icons.qrc @@ -1,31 +1,32 @@ dark_draw-eraser.svg dark_ox16-action-object-align-horizontal-center-calligra.svg dark_ox16-action-object-align-horizontal-left-calligra.svg dark_ox16-action-object-align-horizontal-right-calligra.svg dark_ox16-action-object-align-vertical-bottom-calligra.svg dark_ox16-action-object-align-vertical-center-calligra.svg dark_ox16-action-object-align-vertical-top-calligra.svg dark_ox16-action-object-order-back-calligra.svg dark_ox16-action-object-order-front-calligra.svg dark_ox16-action-object-order-lower-calligra.svg dark_ox16-action-object-order-raise-calligra.svg dark_ox16-action-object-group-calligra.svg dark_ox16-action-object-ungroup-calligra.svg dark_paintop_settings_01.svg dark_paintop_settings_02.svg + dark_pivot-point.svg dark_stroke-cap-butt.svg dark_stroke-cap-round.svg dark_stroke-cap-square.svg dark_stroke-join-bevel.svg dark_stroke-join-miter.svg dark_stroke-join-round.svg dark_symmetry-horizontal.svg dark_symmetry-vertical.svg dark_onionOff.svg dark_onionOn.svg dark_onion_skin_options.svg diff --git a/krita/pics/misc-light/light_pivot-point.svg b/krita/pics/misc-light/light_pivot-point.svg new file mode 100644 index 0000000000..e69a09588e --- /dev/null +++ b/krita/pics/misc-light/light_pivot-point.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/krita/pics/misc-light/misc-light-icons.qrc b/krita/pics/misc-light/misc-light-icons.qrc index 28ec376a65..58f156a5da 100644 --- a/krita/pics/misc-light/misc-light-icons.qrc +++ b/krita/pics/misc-light/misc-light-icons.qrc @@ -1,31 +1,32 @@ light_draw-eraser.svg light_ox16-action-object-align-horizontal-center-calligra.svg light_ox16-action-object-align-horizontal-left-calligra.svg light_ox16-action-object-align-horizontal-right-calligra.svg light_ox16-action-object-align-vertical-bottom-calligra.svg light_ox16-action-object-align-vertical-center-calligra.svg light_ox16-action-object-align-vertical-top-calligra.svg light_ox16-action-object-order-back-calligra.svg light_ox16-action-object-order-front-calligra.svg light_ox16-action-object-order-lower-calligra.svg light_ox16-action-object-order-raise-calligra.svg light_ox16-action-object-group-calligra.svg light_ox16-action-object-ungroup-calligra.svg light_paintop_settings_01.svg light_paintop_settings_02.svg + light_pivot-point.svg light_stroke-cap-butt.svg light_stroke-cap-round.svg light_stroke-cap-square.svg light_stroke-join-bevel.svg light_stroke-join-miter.svg light_stroke-join-round.svg light_symmetry-horizontal.svg light_symmetry-vertical.svg light_onionOff.svg light_onionOn.svg light_onion_skin_options.svg diff --git a/krita/pics/tool_transform/dark_transform_icons_cage.png b/krita/pics/tool_transform/dark_transform_icons_cage.png deleted file mode 100644 index 67dfde90ac..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_cage.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_cage.svg b/krita/pics/tool_transform/dark_transform_icons_cage.svg index d177e0418b..34d2f4ce3e 100644 --- a/krita/pics/tool_transform/dark_transform_icons_cage.svg +++ b/krita/pics/tool_transform/dark_transform_icons_cage.svg @@ -1,107 +1,67 @@ - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="1884" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1"> + + + id="metadata4146"> image/svg+xml - + - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_erase.png b/krita/pics/tool_transform/dark_transform_icons_liquify_erase.png deleted file mode 100644 index 8f96447a86..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_erase.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_erase.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_erase.svg index 6c37808759..5a6af139a1 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_erase.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_erase.svg @@ -1,126 +1,67 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="dark_transform_icons_liquify_erase.svg"> - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_main.png b/krita/pics/tool_transform/dark_transform_icons_liquify_main.png deleted file mode 100644 index abe0d75c13..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_main.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_main.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_main.svg index 2cdb3f1849..90aaa22e71 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_main.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_main.svg @@ -1,115 +1,67 @@ - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_move.png b/krita/pics/tool_transform/dark_transform_icons_liquify_move.png deleted file mode 100644 index 5461629a47..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_move.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_move.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_move.svg index fd8b9a49a7..33cf35837c 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_move.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_move.svg @@ -1,438 +1,67 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_offset.png b/krita/pics/tool_transform/dark_transform_icons_liquify_offset.png deleted file mode 100644 index a0d34d6574..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_offset.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_offset.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_offset.svg index d902515349..b743613be8 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_offset.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_offset.svg @@ -1,223 +1,77 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_resize.png b/krita/pics/tool_transform/dark_transform_icons_liquify_resize.png deleted file mode 100644 index 7f8e3b759c..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_resize.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_resize.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_resize.svg index 0a232438b0..a9415605fc 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_resize.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_resize.svg @@ -1,127 +1,82 @@ - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.png b/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.png deleted file mode 100644 index dd55714d53..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.svg index d14e777b83..c552a0df92 100644 --- a/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.svg +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_rotate.svg @@ -1,152 +1,73 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="dark_transform_icons_liquify_rotate.svg"> - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.png b/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.png deleted file mode 100644 index 17f17fe04a..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.svg b/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.svg new file mode 100644 index 0000000000..aadd876e95 --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_liquify_rotateCCW.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_main.png b/krita/pics/tool_transform/dark_transform_icons_main.png deleted file mode 100644 index 473eff13a9..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_main.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_main.svg b/krita/pics/tool_transform/dark_transform_icons_main.svg index d9149e5f9b..fe271ad1bf 100644 --- a/krita/pics/tool_transform/dark_transform_icons_main.svg +++ b/krita/pics/tool_transform/dark_transform_icons_main.svg @@ -1,111 +1,67 @@ + - - - - - - - - - + inkscape:zoom="15.839192" + inkscape:cx="8.0160905" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - diff --git a/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg b/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg index 5c856aef1b..c1ca2ab01f 100644 --- a/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg +++ b/krita/pics/tool_transform/dark_transform_icons_mirror_x.svg @@ -1,160 +1,78 @@ + id="defs5504" /> - - - - - - - - - - - - - - - - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - + + transform="translate(0,-1030.3622)"> + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 7.984375 3 L 0 19 L 8 19 L 7.984375 3 z M 7 8 L 7 18 L 2 18 L 7 8 z " + transform="translate(0,1030.3622)" + id="path4777" /> + inkscape:connector-curvature="0" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 14.015625,1033.3622 7.984375,16 -8,0 0.01563,-16 z m 0.984375,5 0,10 5,0 -5,-10 z" + id="path4777-5" /> diff --git a/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg b/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg index fc91d531f3..7e5b19f1f6 100644 --- a/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg +++ b/krita/pics/tool_transform/dark_transform_icons_mirror_y.svg @@ -1,160 +1,78 @@ + id="defs5504" /> - - - - - - - - - - - - - - - - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - + + transform="translate(0,-1030.3622)"> + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + inkscape:connector-curvature="0" + style="fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 19,1044.3778 -16,7.9844 0,-8 16,0.016 z m -5,0.9844 -10,0 0,5 10,-5 z" + id="path4777-5" /> diff --git a/krita/pics/tool_transform/dark_transform_icons_penPressure.png b/krita/pics/tool_transform/dark_transform_icons_penPressure.png deleted file mode 100644 index e343451822..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_penPressure.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_penPressure.svg b/krita/pics/tool_transform/dark_transform_icons_penPressure.svg index 373d32c5e9..4458747c2b 100644 --- a/krita/pics/tool_transform/dark_transform_icons_penPressure.svg +++ b/krita/pics/tool_transform/dark_transform_icons_penPressure.svg @@ -1,167 +1,74 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="dark_transform_icons_penPressure.svg"> - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="1884" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1"> + + + id="metadata4146"> image/svg+xml - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.png b/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.png deleted file mode 100644 index f18f35ffec..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.svg b/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.svg new file mode 100644 index 0000000000..11d2761ccb --- /dev/null +++ b/krita/pics/tool_transform/dark_transform_icons_penPressure_locked.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_perspective.png b/krita/pics/tool_transform/dark_transform_icons_perspective.png deleted file mode 100644 index 51601de6cf..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_perspective.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_perspective.svg b/krita/pics/tool_transform/dark_transform_icons_perspective.svg index 6eab6ef54a..b1123f863b 100644 --- a/krita/pics/tool_transform/dark_transform_icons_perspective.svg +++ b/krita/pics/tool_transform/dark_transform_icons_perspective.svg @@ -1,167 +1,74 @@ + viewBox="0 0 16 16" + id="svg4141" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="dark_transform_icons_perspective.svg"> + - + inkscape:zoom="22.4" + inkscape:cx="8.0160905" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + - - - - - - - - - - - - - - - - + id="metadata4146"> image/svg+xml - - - diff --git a/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg b/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg index 4404fe36bc..df1c870177 100644 --- a/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg +++ b/krita/pics/tool_transform/dark_transform_icons_rotate_ccw.svg @@ -1,104 +1,79 @@ - - - + id="defs5504" /> + inkscape:window-maximized="1"> - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - + - - - - - + transform="translate(0,-1030.3622)"> + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg b/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg index 951b763f2b..22df4db22a 100644 --- a/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg +++ b/krita/pics/tool_transform/dark_transform_icons_rotate_cw.svg @@ -1,103 +1,80 @@ - - - + id="defs5504" /> + inkscape:window-maximized="1"> - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - + - - - - - + transform="translate(0,-1030.3622)"> + + + diff --git a/krita/pics/tool_transform/dark_transform_icons_warp.png b/krita/pics/tool_transform/dark_transform_icons_warp.png deleted file mode 100644 index acb924013d..0000000000 Binary files a/krita/pics/tool_transform/dark_transform_icons_warp.png and /dev/null differ diff --git a/krita/pics/tool_transform/dark_transform_icons_warp.svg b/krita/pics/tool_transform/dark_transform_icons_warp.svg index 6c0948126c..2319ed5382 100644 --- a/krita/pics/tool_transform/dark_transform_icons_warp.svg +++ b/krita/pics/tool_transform/dark_transform_icons_warp.svg @@ -1,180 +1,67 @@ + - - - - - - - - - - - - - - - - - - + inkscape:zoom="15.839192" + inkscape:cx="7.6812691" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1047" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_cage.png b/krita/pics/tool_transform/light_transform_icons_cage.png deleted file mode 100644 index e9f118ea74..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_cage.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_cage.svg b/krita/pics/tool_transform/light_transform_icons_cage.svg index 929e493f05..2d2892eb63 100644 --- a/krita/pics/tool_transform/light_transform_icons_cage.svg +++ b/krita/pics/tool_transform/light_transform_icons_cage.svg @@ -1,164 +1,67 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_cage.svg"> - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_erase.png b/krita/pics/tool_transform/light_transform_icons_liquify_erase.png deleted file mode 100644 index c2740512fa..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_erase.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_erase.svg b/krita/pics/tool_transform/light_transform_icons_liquify_erase.svg index 28e0e0990d..e1383e9610 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_erase.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_erase.svg @@ -1,219 +1,67 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_erase.svg"> - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - - - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_main.png b/krita/pics/tool_transform/light_transform_icons_liquify_main.png deleted file mode 100644 index 8ac8ce7397..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_main.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_main.svg b/krita/pics/tool_transform/light_transform_icons_liquify_main.svg index 018972d61b..dc360ed7c5 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_main.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_main.svg @@ -1,231 +1,67 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_main.svg"> - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_move.png b/krita/pics/tool_transform/light_transform_icons_liquify_move.png deleted file mode 100644 index 151414d474..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_move.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_move.svg b/krita/pics/tool_transform/light_transform_icons_liquify_move.svg index 9f545e9c1c..b47e2afaf2 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_move.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_move.svg @@ -1,650 +1,67 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_move.svg"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_offset.png b/krita/pics/tool_transform/light_transform_icons_liquify_offset.png deleted file mode 100644 index 142326ebf0..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_offset.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_offset.svg b/krita/pics/tool_transform/light_transform_icons_liquify_offset.svg index 9df4f097b1..394295d5af 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_offset.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_offset.svg @@ -1,373 +1,77 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_offset.svg"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_resize.png b/krita/pics/tool_transform/light_transform_icons_liquify_resize.png deleted file mode 100644 index 7dd29e20c8..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_resize.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_resize.svg b/krita/pics/tool_transform/light_transform_icons_liquify_resize.svg index e0995fa18b..af376bec4e 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_resize.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_resize.svg @@ -1,263 +1,82 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_resize.svg"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_rotate.png b/krita/pics/tool_transform/light_transform_icons_liquify_rotate.png deleted file mode 100644 index a9d4d9af8d..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_rotate.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_rotate.svg b/krita/pics/tool_transform/light_transform_icons_liquify_rotate.svg index 5585c0a92a..1be1990e77 100644 --- a/krita/pics/tool_transform/light_transform_icons_liquify_rotate.svg +++ b/krita/pics/tool_transform/light_transform_icons_liquify_rotate.svg @@ -1,213 +1,73 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_liquify_rotate.svg"> - - - - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.png b/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.png deleted file mode 100644 index ad7e71875e..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.svg b/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.svg new file mode 100644 index 0000000000..7be9c55d3c --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_liquify_rotateCCW.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_main.png b/krita/pics/tool_transform/light_transform_icons_main.png deleted file mode 100644 index 3223442ef3..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_main.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_main.svg b/krita/pics/tool_transform/light_transform_icons_main.svg index 4b8ebb6310..a2280f7566 100644 --- a/krita/pics/tool_transform/light_transform_icons_main.svg +++ b/krita/pics/tool_transform/light_transform_icons_main.svg @@ -1,194 +1,67 @@ + viewBox="0 0 16 16" + id="svg4141" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_main.svg"> + - - - - - - - - - - - - - - - - - - - - - - - + inkscape:zoom="15.839192" + inkscape:cx="8.0160905" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - + - - - diff --git a/krita/pics/tool_transform/light_transform_icons_mirror_x.svg b/krita/pics/tool_transform/light_transform_icons_mirror_x.svg index e82757f6dc..0ec27b8cac 100644 --- a/krita/pics/tool_transform/light_transform_icons_mirror_x.svg +++ b/krita/pics/tool_transform/light_transform_icons_mirror_x.svg @@ -1,160 +1,78 @@ + id="defs5504" /> - - - - - - - - - - - - - - - - - + id="grid6050" /> + id="metadata5507"> image/svg+xml + transform="translate(0,-1030.3622)"> + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> + inkscape:connector-curvature="0" + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 14.0156,1033.3622 7.9844,16 -8,0 0.016,-16 z m 0.9844,5 0,10 5,0 -5,-10 z" + id="path4777-5" /> diff --git a/krita/pics/tool_transform/light_transform_icons_mirror_y.svg b/krita/pics/tool_transform/light_transform_icons_mirror_y.svg index ff64b6ce80..a2e7c85f10 100644 --- a/krita/pics/tool_transform/light_transform_icons_mirror_y.svg +++ b/krita/pics/tool_transform/light_transform_icons_mirror_y.svg @@ -1,164 +1,78 @@ + id="defs5504" /> - - - - - - - - - - - - - - - - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - - - - - + transform="translate(0,-1030.3622)"> + + + diff --git a/krita/pics/tool_transform/light_transform_icons_penPressure.png b/krita/pics/tool_transform/light_transform_icons_penPressure.png deleted file mode 100644 index 0a2c6fef94..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_penPressure.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_penPressure.svg b/krita/pics/tool_transform/light_transform_icons_penPressure.svg index 675a981720..800bcd12af 100644 --- a/krita/pics/tool_transform/light_transform_icons_penPressure.svg +++ b/krita/pics/tool_transform/light_transform_icons_penPressure.svg @@ -1,119 +1,74 @@ + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_penPressure.svg"> - - - - - - - - + id="defs4143" /> + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="1884" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1"> + + + id="metadata4146"> image/svg+xml - - - diff --git a/krita/pics/tool_transform/light_transform_icons_penPressure_locked.png b/krita/pics/tool_transform/light_transform_icons_penPressure_locked.png deleted file mode 100644 index 14b0a62405..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_penPressure_locked.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_penPressure_locked.svg b/krita/pics/tool_transform/light_transform_icons_penPressure_locked.svg new file mode 100644 index 0000000000..0ec2a50139 --- /dev/null +++ b/krita/pics/tool_transform/light_transform_icons_penPressure_locked.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/krita/pics/tool_transform/light_transform_icons_perspective.png b/krita/pics/tool_transform/light_transform_icons_perspective.png deleted file mode 100644 index f7770bc49f..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_perspective.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_perspective.svg b/krita/pics/tool_transform/light_transform_icons_perspective.svg index 7f39210d67..a10df17c9a 100644 --- a/krita/pics/tool_transform/light_transform_icons_perspective.svg +++ b/krita/pics/tool_transform/light_transform_icons_perspective.svg @@ -1,323 +1,74 @@ + viewBox="0 0 16 16" + id="svg4141" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_perspective.svg"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inkscape:zoom="22.4" + inkscape:cx="8.0160905" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1051" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - - - - diff --git a/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg b/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg index e024d99f07..4f1e7dba84 100644 --- a/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg +++ b/krita/pics/tool_transform/light_transform_icons_rotate_ccw.svg @@ -1,104 +1,79 @@ - - - + id="defs5504" /> + inkscape:window-maximized="1"> - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - - - - - + transform="translate(0,-1030.3622)"> + + + diff --git a/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg b/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg index c28bc63983..d8638e06c9 100644 --- a/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg +++ b/krita/pics/tool_transform/light_transform_icons_rotate_cw.svg @@ -1,103 +1,80 @@ - - - + id="defs5504" /> + inkscape:window-maximized="1"> - - + id="grid6050" /> + id="metadata5507"> image/svg+xml - - - - - + transform="translate(0,-1030.3622)"> + + + diff --git a/krita/pics/tool_transform/light_transform_icons_warp.png b/krita/pics/tool_transform/light_transform_icons_warp.png deleted file mode 100644 index 7006b17c2f..0000000000 Binary files a/krita/pics/tool_transform/light_transform_icons_warp.png and /dev/null differ diff --git a/krita/pics/tool_transform/light_transform_icons_warp.svg b/krita/pics/tool_transform/light_transform_icons_warp.svg index 140b472948..6c3e54350f 100644 --- a/krita/pics/tool_transform/light_transform_icons_warp.svg +++ b/krita/pics/tool_transform/light_transform_icons_warp.svg @@ -1,389 +1,67 @@ + viewBox="0 0 16 16" + id="svg4141" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="light_transform_icons_warp.svg"> + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inkscape:zoom="15.839192" + inkscape:cx="7.6812691" + inkscape:cy="7.3105687" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="true" + units="px" + inkscape:window-width="938" + inkscape:window-height="1047" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0"> + + + id="metadata4146"> image/svg+xml - - + inkscape:label="Calque 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-1036.3622)"> - + style="fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 0,1036.3622 0,16 12,0 4,-8 -4,-8 -12,0 z m 2,2 3,0 2,5 -5,0 0,-5 z m 5,0 4,0 3,5 -5,0 -2,-5 z m -5,7 5,0 -2,5 -3,0 0,-5 z m 7,0 5,0 -3,5 -4,0 2,-5 z" + id="path4155" /> - - - - - - diff --git a/krita/pics/tool_transform/tool-transform-icons.qrc b/krita/pics/tool_transform/tool-transform-icons.qrc index 023352cce6..f742da059e 100644 --- a/krita/pics/tool_transform/tool-transform-icons.qrc +++ b/krita/pics/tool_transform/tool-transform-icons.qrc @@ -1,40 +1,40 @@ - dark_transform_icons_cage.png - dark_transform_icons_liquify_erase.png - dark_transform_icons_liquify_main.png - dark_transform_icons_liquify_move.png - dark_transform_icons_liquify_offset.png - dark_transform_icons_liquify_resize.png - dark_transform_icons_liquify_rotate.png - dark_transform_icons_liquify_rotateCCW.png - dark_transform_icons_main.png - dark_transform_icons_perspective.png - dark_transform_icons_warp.png - dark_transform_icons_penPressure.png + dark_transform_icons_cage.svg + dark_transform_icons_liquify_erase.svg + dark_transform_icons_liquify_main.svg + dark_transform_icons_liquify_move.svg + dark_transform_icons_liquify_offset.svg + dark_transform_icons_liquify_resize.svg + dark_transform_icons_liquify_rotate.svg + dark_transform_icons_liquify_rotateCCW.svg + dark_transform_icons_main.svg + dark_transform_icons_perspective.svg + dark_transform_icons_warp.svg + dark_transform_icons_penPressure.svg dark_krita_tool_transform_recursive.png dark_transform_icons_mirror_x.svg dark_transform_icons_mirror_y.svg dark_transform_icons_rotate_cw.svg dark_transform_icons_rotate_ccw.svg - light_transform_icons_cage.png - light_transform_icons_liquify_erase.png - light_transform_icons_liquify_main.png - light_transform_icons_liquify_move.png - light_transform_icons_liquify_offset.png - light_transform_icons_liquify_resize.png - light_transform_icons_liquify_rotate.png - light_transform_icons_liquify_rotateCCW.png - light_transform_icons_main.png - light_transform_icons_perspective.png - light_transform_icons_warp.png - light_transform_icons_penPressure.png + light_transform_icons_cage.svg + light_transform_icons_liquify_erase.svg + light_transform_icons_liquify_main.svg + light_transform_icons_liquify_move.svg + light_transform_icons_liquify_offset.svg + light_transform_icons_liquify_resize.svg + light_transform_icons_liquify_rotate.svg + light_transform_icons_liquify_rotateCCW.svg + light_transform_icons_main.svg + light_transform_icons_perspective.svg + light_transform_icons_warp.svg + light_transform_icons_penPressure.svg light_krita_tool_transform_recursive.png light_transform_icons_mirror_x.svg light_transform_icons_mirror_y.svg light_transform_icons_rotate_cw.svg light_transform_icons_rotate_ccw.svg - dark_transform_icons_penPressure_locked.png - light_transform_icons_penPressure_locked.png + dark_transform_icons_penPressure_locked.svg + light_transform_icons_penPressure_locked.svg diff --git a/krita/pics/tool_transform/transform_icons_base.svg b/krita/pics/tool_transform/transform_icons_base.svg deleted file mode 100644 index 0dccecf7f7..0000000000 --- a/krita/pics/tool_transform/transform_icons_base.svg +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/krita_xgettext.sh b/kundo2_aware_xgettext.sh similarity index 67% rename from krita_xgettext.sh rename to kundo2_aware_xgettext.sh index 7b0e3467ef..99cb4cd773 100644 --- a/krita_xgettext.sh +++ b/kundo2_aware_xgettext.sh @@ -1,96 +1,113 @@ # -# Helper function for extracting translatable messages from krita source code. -# Usage: krita_xgettext +# Helper function for extracting translatable messages from Calligra/Krita/Kexi source code. +# Usage: kundo2_aware_xgettext # If there are no messages or the is empty, the pot file is deleted. # # Example usage that creates $podir/myapp.pot file: -# krita_xgettext myapp.pot `find . -name \*.cpp -o -name \*.h` +# kundo2_aware_xgettext myapp.pot `find . -name \*.cpp -o -name \*.h` # -function krita_xgettext() { +function kundo2_aware_xgettext() { POTFILE="$podir/$1" shift if test -n "$*"; then - krita_xgettext_internal $* | tee "${POTFILE}" | tail -n1 | grep "^msgstr \"\"\$" > /dev/null \ + # we rely on last line being a 'msgstr' signaling that strings has been extracted (a header is always present) + # normally it ends with 'msgstr ""' but if plural it can end with eg 'msgstr[1] ""' + kundo2_aware_xgettext_internal $* | tee "${POTFILE}" | tail -n1 | grep "^msgstr" > /dev/null \ || rm -f "${POTFILE}" 2> /dev/null fi } # How to unit test: # export podir=. # cp init-sample.pot sample.pot # source krita_xgettext.sh # add_ctxt_qtundo sample.pot # # Then check that all messages in sample.pot have "(qtundo-format)" in msgctxt. function add_ctxt_qtundo() { POT_PART_QUNDOFORMAT="$1" POT_PART_QUNDOFORMAT2="`mktemp $podir/_qundoformat2_XXXXXXXX.pot`" # Prepend "(qtundo-format)" to existing msgctxt properties of messages sed -i -e 's/^msgctxt "/msgctxt "(qtundo-format) /' "${POT_PART_QUNDOFORMAT}" # Add msgctxt "(qtundo-format)" to messages not having msgctxt yet # # lastLine != "#, fuzzy" is the check for the .pot header. + # If lastLine starts with '"' the msgctxt has been split on several lines and is treated by sed above, so skip it mv "${POT_PART_QUNDOFORMAT}" "${POT_PART_QUNDOFORMAT2}" cat "${POT_PART_QUNDOFORMAT2}" | awk ' /^msgid "/ { - if (lastLine !~ /^msgctxt/ && lastLine !~ /^"/ && lastLine != "#, fuzzy") { + if (lastLine !~ /^\"/ && lastLine !~ /^msgctxt/ && lastLine != "#, fuzzy") { print "msgctxt \"(qtundo-format)\"" } } { print ; lastLine = $0 }' > "${POT_PART_QUNDOFORMAT}" rm -f "${POT_PART_QUNDOFORMAT2}" } -function krita_xgettext_internal() { +function kundo2_aware_xgettext_internal() { SRC_FILES="$*" POT_PART_NORMAL="`mktemp $podir/_normal_XXXXXXXX.pot`" POT_PART_QUNDOFORMAT="`mktemp $podir/_qundoformat_XXXXXXXX.pot`" POT_MERGED="`mktemp $podir/_merged_XXXXXXXX.pot`" $XGETTEXT ${CXG_EXTRA_ARGS} ${SRC_FILES} -o "${POT_PART_NORMAL}" --force-po - $XGETTEXT_PROGRAM --from-code=UTF-8 -C --kde -kkundo2_i18n:1 -kkundo2_i18np:1,2 -kkundo2_i18nc:1c,2 -kkundo2_i18ncp:1c,2,3 ${CXG_EXTRA_ARGS} ${SRC_FILES} -o "${POT_PART_QUNDOFORMAT}" + + XGETTEXT_FLAGS_KUNDO2="\ +--copyright-holder=This_file_is_part_of_KDE \ +--msgid-bugs-address=http://bugs.kde.org \ +--from-code=UTF-8 +-C -k --kde \ +-kkundo2_i18n:1 -kkundo2_i18np:1,2 -kkundo2_i18nc:1c,2 -kkundo2_i18ncp:1c,2,3 \ +" + + $XGETTEXT_PROGRAM ${XGETTEXT_FLAGS_KUNDO2} ${CXG_EXTRA_ARGS} ${SRC_FILES} -o "${POT_PART_QUNDOFORMAT}" if [ $(cat ${POT_PART_NORMAL} ${POT_PART_QUNDOFORMAT} | grep -c \(qtundo-format\)) != 0 ]; then echo "ERROR: Context '(qtundo-format)' should not be added manually. Use kundo2_i18n*() calls instead." 1>&2 exit 17 fi if [ -s "${POT_PART_QUNDOFORMAT}" ]; then add_ctxt_qtundo "${POT_PART_QUNDOFORMAT}" fi if [ -s "${POT_PART_NORMAL}" -a -s "${POT_PART_QUNDOFORMAT}" ]; then + # ensure an empty line or else KDE_HEADER search will fail + # in case POT_PART_NORMAL only contains header + echo "" >>${POT_PART_NORMAL} + ${MSGCAT} -F "${POT_PART_NORMAL}" "${POT_PART_QUNDOFORMAT}" > ${POT_MERGED} MERGED_HEADER_LINE_COUNT=$(cat ${POT_MERGED} | grep "^$" -B 100000 --max-count=1 | wc -l) - KDE_HEADER="$(cat ${POT_PART_NORMAL} | grep "^$" -B 100000 --max-count=1)" MERGED_TAIL="$(cat ${POT_MERGED} | tail -n +$MERGED_HEADER_LINE_COUNT)" # Print out the resulting .pot echo "$KDE_HEADER" echo "$MERGED_TAIL" elif [ -s "${POT_PART_NORMAL}" ]; then + echo "# POT_PART_NORMAL only" cat "${POT_PART_NORMAL}" elif [ -s "${POT_PART_QUNDOFORMAT}" ]; then + echo "# POT_PART_QUNDOFORMAT only" cat "${POT_PART_QUNDOFORMAT}" fi rm -f "${POT_PART_NORMAL}" "${POT_PART_QUNDOFORMAT}" "${POT_MERGED}" } # Sets EXCLUDE variable to excludes compatible with the find(1) command, e.g. '-path a -o -path b'. # To unconditionally exclude dir (with subdirs) just put an empty file .i18n in it. -# To exclude dir for all translations but one, e.g. foo.pot, put a single "foo" line into the .i18n file. +# To disable excluding for given file, e.g. foo.pot, add "foo.pot" line to the .i18n file. function find_exclude() { EXCLUDE="" for f in `find . -name .i18n | sed 's/\/\.i18n$//g' | sort`; do if ! grep -q "^${1}$" "$f/.i18n" ; then if [ -n "$EXCLUDE" ] ; then EXCLUDE="$EXCLUDE -o " ; fi EXCLUDE="$EXCLUDE -path $f" fi done if [ -z "$EXCLUDE" ] ; then EXCLUDE="-path __dummy__" ; fi # needed because -prune in find needs args } diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index 63e2c79e51..cc422bb081 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1692 +1,1692 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *undo, KisImageAnimationInterface *_animationInterface) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8()) , nserver(1) , undoStore(undo ? undo : new KisDumbUndoStore()) , legacyUndoAdapter(undoStore.data(), _q) , postExecutionUndoAdapter(undoStore.data(), _q) , recorder(_q) , signalRouter(_q) , animationInterface(_animationInterface) , scheduler(_q) , axesCenter(QPointF(0.5, 0.5)) { { KisImageConfig cfg; if (cfg.enableProgressReporting()) { scheduler.setProgressProxy(&compositeProgressProxy); } // Each of these lambdas defines a new factory function. scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q))); }); scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q))); }); scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q))); }); } connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } ~KisImagePrivate() { /** * Stop animation interface. It may use the rootLayer. */ delete animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ rootLayer.clear(); } KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; QScopedPointer undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; QPointF axesCenter; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() , m_d(new KisImagePrivate(this, width, height, colorSpace, undoStore, new KisImageAnimationInterface(this))) { setObjectName(name); setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); delete m_d; disconnect(); // in case Qt gets confused } KisImage *KisImage::clone(bool exactCopy) { return new KisImage(*this, 0, exactCopy); } KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy) : KisNodeFacade(), KisNodeGraphListener(), KisShared(), m_d(new KisImagePrivate(this, rhs.width(), rhs.height(), rhs.colorSpace(), undoStore ? undoStore : new KisDumbUndoStore(), new KisImageAnimationInterface(*rhs.animationInterface(), this))) { setObjectName(rhs.objectName()); m_d->xres = rhs.m_d->xres; m_d->yres = rhs.m_d->yres; if (rhs.m_d->proofingConfig) { m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig)); } KisNodeSP newRoot = rhs.root()->clone(); newRoot->setGraphListener(this); newRoot->setImage(this); m_d->rootLayer = dynamic_cast(newRoot.data()); setRoot(newRoot); if (exactCopy) { QQueue linearizedNodes; KisLayerUtils::recursiveApplyNodes(rhs.root(), [&linearizedNodes](KisNodeSP node) { linearizedNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(newRoot, [&linearizedNodes](KisNodeSP node) { KisNodeSP refNode = linearizedNodes.dequeue(); node->setUuid(refNode->uuid()); }); } Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) { m_d->compositions << toQShared(new KisLayerComposition(*comp, this)); } rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver); vKisAnnotationSP newAnnotations; Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) { newAnnotations << annotation->clone(); } m_d->annotations = newAnnotations; KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals); KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests); m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail; } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } -bool KisImage::isIdle() +bool KisImage::isIdle(bool allowLocked) { - return !locked() && m_d->scheduler.isIdle(); + return (allowLocked || !locked()) && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = qobject_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); shearImpl(kundo2_i18n("Shear layer"), node, false, angleX, angleY, shearOrigin); } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); return m_d->rootLayer->accept(visitor); } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QRect KisImage::documentToIntPixel(const QRectF &documentRect) const { return documentToPixel(documentRect).toAlignedRect(); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { const int lod = currentLevelOfDetail(); return lod > 0 ? m_d->scheduler.lodNPostExecutionUndoAdapter() : &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); m_d->undoStore.reset(undoStore); } KisUndoStore* KisImage::undoStore() { return m_d->undoStore.data(); } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } UndoResult KisImage::tryUndoUnfinishedLod0Stroke() { return m_d->scheduler.tryUndoLastStrokeAsync(); } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // udpate filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QRect &rect, const QRect &cropRect) { if (rect.isEmpty()) return; KisNodeGraphListener::requestProjectionUpdate(node, rect); m_d->scheduler.updateProjection(node, rect, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rect)) { return; } m_d->animationInterface->notifyNodeChanged(node, rect, false); /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { const QRect boundRect = effectiveLodBounds(); KisWrappedRect splitRect(rect, boundRect); Q_FOREACH (const QRect &rc, splitRect) { requestProjectionUpdateImpl(node, rc, boundRect); } } else { requestProjectionUpdateImpl(node, rect, bounds()); } } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (!m_d->proofingConfig) { KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } return m_d->proofingConfig; } QPointF KisImage::mirrorAxesCenter() const { return m_d->axesCenter; } void KisImage::setMirrorAxesCenter(const QPointF &value) const { m_d->axesCenter = value; } diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h index d420171435..a34ccf6054 100644 --- a/libs/image/kis_image.h +++ b/libs/image/kis_image.h @@ -1,984 +1,984 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGE_H_ #define KIS_IMAGE_H_ #include #include #include #include #include #include #include #include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here #include "kis_types.h" #include "kis_shared.h" #include "kis_node_graph_listener.h" #include "kis_node_facade.h" #include "kis_image_interfaces.h" #include "kis_strokes_queue_undo_result.h" #include class KisDocument; class KoColorSpace; class KoColor; class KisCompositeProgressProxy; class KisActionRecorder; class KisUndoStore; class KisUndoAdapter; class KisImageSignalRouter; class KisPostExecutionUndoAdapter; class KisFilterStrategy; class KoColorProfile; class KisLayerComposition; class KisSpontaneousJob; class KisImageAnimationInterface; class KUndo2MagicString; class KisProofingConfiguration; namespace KisMetaData { class MergeStrategy; } /** * This is the image class, it contains a tree of KisLayer stack and * meta information about the image. And it also provides some * functions to manipulate the whole image. */ class KRITAIMAGE_EXPORT KisImage : public QObject, public KisStrokesFacade, public KisStrokeUndoFacade, public KisUpdatesFacade, public KisProjectionUpdateListener, public KisNodeFacade, public KisNodeGraphListener, public KisShared { Q_OBJECT public: /// @param colorSpace can be null. in that case it will be initialised to a default color space. KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name); virtual ~KisImage(); public: // KisNodeGraphListener implementation void aboutToAddANode(KisNode *parent, int index); void nodeHasBeenAdded(KisNode *parent, int index); void aboutToRemoveANode(KisNode *parent, int index); void nodeChanged(KisNode * node); void invalidateAllFrames(); void notifySelectionChanged(); void requestProjectionUpdate(KisNode *node, const QRect& rect); void invalidateFrames(const KisTimeRange &range, const QRect &rect); void requestTimeSwitch(int time); public: // KisProjectionUpdateListener implementation void notifyProjectionUpdated(const QRect &rc); public: /** * Makes a copy of the image with all the layers. If possible, shallow * copies of the layers are made. * * \p exactCopy shows if the copied image should look *exactly* the same as * the other one (according to it's .kra xml representation). It means that * the layers will have the same UUID keys and, therefore, you are not * expected to use the copied image anywhere except for saving. Don't use * this option if you plan to work with the copied image later. */ KisImage* clone(bool exactCopy = false); /** * Render the projection onto a QImage. */ QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile * profile); /** * Render the projection onto a QImage. * (this is an overloaded function) */ QImage convertToQImage(QRect imageRect, const KoColorProfile * profile); /** * XXX: docs! */ QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile); /** * Calls KisUpdateScheduler::lock (XXX: APIDOX -- what does that mean?) */ void lock(); /** * Calls KisUpdateScheduler::unlock (XXX: APIDOX -- what does that mean?) */ void unlock(); /** * Returns true if lock() has been called more often than unlock(). */ bool locked() const; /** * @return the global selection object or 0 if there is none. The * global selection is always read-write. */ KisSelectionSP globalSelection() const; /** * Retrieve the next automatic layername (XXX: fix to add option to return Mask X) */ QString nextLayerName(const QString &baseName = "") const; /** * Set the automatic layer name counter one back. */ void rollBackLayerName(); /** * Resize the image to the specified rect. The resize * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void resizeImage(const QRect& newRect); /** * Crop the image to the specified rect. The crop * method handles the creating on an undo step itself. * * @param newRect the rect describing the new width, height and offset * of the image */ void cropImage(const QRect& newRect); /** * Crop a node to @newRect. The node will *not* be moved anywhere, * it just drops some content */ void cropNode(KisNodeSP node, const QRect& newRect); /// XXX: ApiDox void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy); /// XXX: ApiDox void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy); /** * Execute a rotate transform on all layers in this image. * Image is resized to fit rotated image. */ void rotateImage(double radians); /** * Execute a rotate transform on on a subtree of this image. * Image is not resized. */ void rotateNode(KisNodeSP node, double radians); /** * Execute a shear transform on all layers in this image. */ void shear(double angleX, double angleY); /** * Shear a node and all its children. * @param angleX, @param angleY are given in degrees. */ void shearNode(KisNodeSP node, double angleX, double angleY); /** * Convert the image and all its layers to the dstColorSpace */ void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags); /** * Set the color space of the projection (and the root layer) * to dstColorSpace. No conversion is done for other layers, * their colorspace can differ. * NOTE: Note conversion is done, only regeneration, so no rendering * intent needed */ void convertProjectionColorSpace(const KoColorSpace *dstColorSpace); // Get the profile associated with this image const KoColorProfile * profile() const; /** * Set the profile of the image to the new profile and do the same for * all layers that have the same colorspace and profile of the image. * It doesn't do any pixel conversion. * * This is essential if you have loaded an image that didn't * have an embedded profile to which you want to attach the right profile. * * This does not create an undo action; only call it when creating or * loading an image. * * @returns false if the profile could not be assigned */ bool assignImageProfile(const KoColorProfile *profile); /** * Returns the current undo adapter. You can add new commands to the * undo stack using the adapter. This adapter is used for a backward * compatibility for old commands created before strokes. It blocks * all the porcessing at the scheduler, waits until it's finished * adn executes commands exclusively. */ KisUndoAdapter* undoAdapter() const; /** * This adapter is used by the strokes system. The commands are added * to it *after* redo() is done (in the scheduler context). They are * wrapped into a special command and added to the undo stack. redo() * in not called. */ KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const; /** * Replace current undo store with the new one. The old store * will be deleted. * This method is used by KisDocument for dropping all the commands * during file loading. */ void setUndoStore(KisUndoStore *undoStore); /** * Return current undo store of the image */ KisUndoStore* undoStore(); /** * @return the action recorder associated with this image */ KisActionRecorder* actionRecorder() const; /** * Tell the image it's modified; this emits the sigImageModified * signal. This happens when the image needs to be saved */ void setModified(); /** * The default colorspace of this image: new layers will have this * colorspace and the projection will have this colorspace. */ const KoColorSpace * colorSpace() const; /** * X resolution in pixels per pt */ double xRes() const; /** * Y resolution in pixels per pt */ double yRes() const; /** * Set the resolution in pixels per pt. */ void setResolution(double xres, double yres); /** * Convert a document coordinate to a pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPointF documentToPixel(const QPointF &documentCoord) const; /** * Convert a document coordinate to an integer pixel coordinate. * * @param documentCoord PostScript Pt coordinate to convert. */ QPoint documentToIntPixel(const QPointF &documentCoord) const; /** * Convert a document rectangle to a pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRectF documentToPixel(const QRectF &documentRect) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param documentRect PostScript Pt rectangle to convert. */ QRect documentToIntPixel(const QRectF &documentRect) const; /** * Convert a pixel coordinate to a document coordinate. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPointF &pixelCoord) const; /** * Convert an integer pixel coordinate to a document coordinate. * The document coordinate is at the centre of the pixel. * * @param pixelCoord pixel coordinate to convert. */ QPointF pixelToDocument(const QPoint &pixelCoord) const; /** * Convert a document rectangle to an integer pixel rectangle. * * @param pixelCoord pixel coordinate to convert. */ QRectF pixelToDocument(const QRectF &pixelCoord) const; /** * Return the width of the image */ qint32 width() const; /** * Return the height of the image */ qint32 height() const; /** * Return the size of the image */ QSize size() const { return QSize(width(), height()); } /** * @return the root node of the image node graph */ KisGroupLayerSP rootLayer() const; /** * Return the projection; that is, the complete, composited * representation of this image. */ KisPaintDeviceSP projection() const; /** * Return the number of layers (not other nodes) that are in this * image. */ qint32 nlayers() const; /** * Return the number of layers (not other node types) that are in * this image and that are hidden. */ qint32 nHiddenLayers() const; /** * Merge all visible layers and discard hidden ones. */ void flatten(); /** * Merge the specified layer with the layer * below this layer, remove the specified layer. */ void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy); /** * flatten the layer: that is, the projection becomes the layer * and all subnodes are removed. If this is not a paint layer, it will morph * into a paint layer. */ void flattenLayer(KisLayerSP layer); /** * Merges layers in \p mergedLayers and creates a new layer above * \p putAfter */ void mergeMultipleLayers(QList mergedLayers, KisNodeSP putAfter); /// @return the exact bounds of the image in pixel coordinates. QRect bounds() const; /** * Returns the actual bounds of the image, taking LevelOfDetail * into account. This value is used as a bounds() value of * KisDefaultBounds object. */ QRect effectiveLodBounds() const; /// use if the layers have changed _completely_ (eg. when flattening) void notifyLayersChanged(); /** * Sets the default color of the root layer projection. All the layers * will be merged on top of this very color */ void setDefaultProjectionColor(const KoColor &color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; void setRootLayer(KisGroupLayerSP rootLayer); /** * Add an annotation for this image. This can be anything: Gamma, EXIF, etc. * Note that the "icc" annotation is reserved for the color strategies. * If the annotation already exists, overwrite it with this one. */ void addAnnotation(KisAnnotationSP annotation); /** get the annotation with the given type, can return 0 */ KisAnnotationSP annotation(const QString& type); /** delete the annotation, if the image contains it */ void removeAnnotation(const QString& type); /** * Start of an iteration over the annotations of this image (including the ICC Profile) */ vKisAnnotationSP_it beginAnnotations(); /** end of an iteration over the annotations of this image */ vKisAnnotationSP_it endAnnotations(); /** * Called before the image is delted and sends the sigAboutToBeDeleted signal */ void notifyAboutToBeDeleted(); KisImageSignalRouter* signalRouter(); /** * Returns whether we can reselect current global selection * * \see reselectGlobalSelection() */ bool canReselectGlobalSelection(); /** * Returns the layer compositions for the image */ QList compositions(); /** * Adds a new layer composition, will be saved with the image */ void addComposition(KisLayerCompositionSP composition); /** * Remove the layer compostion */ void removeComposition(KisLayerCompositionSP composition); /** * Permit or deny the wrap-around mode for all the paint devices * of the image. Note that permitting the wraparound mode will not * necessarily activate it right now. To be activated the wrap * around mode should be 1) permitted; 2) supported by the * currently running stroke. */ void setWrapAroundModePermitted(bool value); /** * \return whether the wrap-around mode is permitted for this * image. If the wrap around mode is permitted and the * currently running stroke supports it, the mode will be * activated for all paint devices of the image. * * \see setWrapAroundMode */ bool wrapAroundModePermitted() const; /** * \return whether the wraparound mode is activated for all the * devices of the image. The mode is activated when both * factors are true: the user permitted it and the stroke * supports it */ bool wrapAroundModeActive() const; /** * \return curent level of detail which is used when processing the image. * Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is * null, which means we work on the original image. */ int currentLevelOfDetail() const; /** * Notify KisImage which level of detail should be used in the * lod-mode. Setting the mode does not guarantee the LOD to be * used. It will be activated only when the stokes supports it. */ void setDesiredLevelOfDetail(int lod); /** * Relative position of the mirror axis center * 0,0 - topleft corner of the image * 1,1 - bottomright corner of the image */ QPointF mirrorAxesCenter() const; /** * Sets the relative position of the axes center * \see mirrorAxesCenter() for details */ void setMirrorAxesCenter(const QPointF &value) const; public Q_SLOTS: /** * Explicitly start regeneration of LoD planes of all the devices * in the image. This call should be performed when the user is idle, * just to make the quality of image updates better. */ void explicitRegenerateLevelOfDetail(); public: /** * Blocks usage of level of detail functionality. After this method * has been called, no new strokes will use LoD. */ void setLevelOfDetailBlocked(bool value); /** * \see setLevelOfDetailBlocked() */ bool levelOfDetailBlocked() const; /** * Notifies that the node collapsed state has changed */ void notifyNodeCollpasedChanged(); KisImageAnimationInterface *animationInterface() const; /** * @brief setProofingConfiguration, this sets the image's proofing configuration, and signals * the proofingConfiguration has changed. * @param proofingConfig - the kis proofing config that will be used instead. */ void setProofingConfiguration(KisProofingConfigurationSP proofingConfig); /** * @brief proofingConfiguration * @return the proofing configuration of the image. */ KisProofingConfigurationSP proofingConfiguration() const; public: bool startIsolatedMode(KisNodeSP node); void stopIsolatedMode(); KisNodeSP isolatedModeRoot() const; Q_SIGNALS: /** * Emitted whenever an action has caused the image to be * recomposited. * * @param rc The rect that has been recomposited. */ void sigImageUpdated(const QRect &); /** Emitted whenever the image has been modified, so that it doesn't match with the version saved on disk. */ void sigImageModified(); /** * The signal is emitted when the size of the image is changed. * \p oldStillPoint and \p newStillPoint give the receiver the * hint about how the new and old rect of the image correspond to * each other. They specify the point of the image around which * the conversion was done. This point will stay still on the * user's screen. That is the \p newStillPoint of the new image * will be painted at the same screen position, where \p * oldStillPoint of the old image was painted. * * \param oldStillPoint is a still point represented in *old* * image coordinates * * \param newStillPoint is a still point represented in *new* * image coordinates */ void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint); void sigProfileChanged(const KoColorProfile * profile); void sigColorSpaceChanged(const KoColorSpace* cs); void sigResolutionChanged(double xRes, double yRes); void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes); /** * Inform the model that a node was changed */ void sigNodeChanged(KisNodeSP node); /** * Inform that the image is going to be deleted */ void sigAboutToBeDeleted(); /** * The signal is emitted right after a node has been connected * to the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. If you need * information about the parent/siblings of the node connect * with Qt::DirectConnection, get needed information and then * emit another Qt::AutoConnection signal to pass this information * to your thread. See details of the implementation * in KisDummiesfacadeBase. */ void sigNodeAddedAsync(KisNodeSP node); /** * This signal is emitted right before a node is going to removed * from the graph of the nodes. * * WARNING: you must not request any graph-related information * about the node being run in a not-scheduler thread. * * \see comment in sigNodeAddedAsync() */ void sigRemoveNodeAsync(KisNodeSP node); /** * Emitted when the root node of the image has changed. * It happens, e.g. when we flatten the image. When * this happens the receiver should reload information * about the image */ void sigLayersChangedAsync(); /** * Emitted when the UI has requested the undo of the last stroke's * operation. The point is, we cannot deal with the internals of * the stroke without its creator knowing about it (which most * probably cause a crash), so we just forward this request from * the UI to the creator of the stroke. * * If your tool supports undoing part of its work, just listen to * this signal and undo when it comes */ void sigUndoDuringStrokeRequested(); /** * Emitted when the UI has requested the cancellation of * the stroke. The point is, we cannot cancel the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports cancelling of its work in the middle * of operation, just listen to this signal and cancel * the stroke when it comes */ void sigStrokeCancellationRequested(); /** * Emitted when the image decides that the stroke should better * be ended. The point is, we cannot just end the stroke * without its creator knowing about it (which most probably * cause a crash), so we just forward this request from the UI * to the creator of the stroke. * * If your tool supports long strokes that may involve multiple * mouse actions in one stroke, just listen to this signal and * end the stroke when it comes. */ void sigStrokeEndRequested(); /** * Same as sigStrokeEndRequested() but is not emitted when the active node * is changed. */ void sigStrokeEndRequestedActiveNodeFiltered(); /** * Emitted when the isolated mode status has changed. * * Can be used by the receivers to catch a fact of forcefully * stopping the isolated mode by the image when some complex * action was requested */ void sigIsolatedModeChanged(); /** * Emitted when one or more nodes changed the collapsed state * */ void sigNodeCollapsedChanged(); /** * Emitted when the proofing configuration of the image is being changed. * */ void sigProofingConfigChanged(); public Q_SLOTS: KisCompositeProgressProxy* compositeProgressProxy(); - bool isIdle(); + bool isIdle(bool allowLocked = false); /** * @brief barrierLock APIDOX * @param readOnly */ void barrierLock(bool readOnly = false); /** * @brief barrierLock APIDOX * @param readOnly */ bool tryBarrierLock(bool readOnly = false); /** * @brief barrierLock APIDOX * @param readOnly */ void waitForDone(); KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy); void addJob(KisStrokeId id, KisStrokeJobData *data); void endStroke(KisStrokeId id); bool cancelStroke(KisStrokeId id); /** * @brief blockUpdates block updating the image projection */ void blockUpdates(); /** * @brief unblockUpdates unblock updating the image project. This * only restarts the scheduler and does not schedule a full refresh. */ void unblockUpdates(); /** * Disables notification of the UI about the changes in the image. * This feature is used by KisProcessingApplicator. It is needed * when we change the size of the image. In this case, the whole * image will be reloaded into UI by sigSizeChanged(), so there is * no need to inform the UI about individual dirty rects. */ void disableUIUpdates(); /** * \see disableUIUpdates */ void enableUIUpdates(); /** * Disables the processing of all the setDirty() requests that * come to the image. The incoming requests are effectively * *dropped*. * * This feature is used by KisProcessingApplicator. For many cases * it provides its own updates interface, which recalculates the * whole subtree of nodes. But while we change any particular * node, it can ask for an update itself. This method is a way of * blocking such intermediate (and excessive) requests. * * NOTE: this is a convenience function for setProjectionUpdatesFilter() * that installs a predefined filter that eats everything. Please * note that these calls are *not* recursive */ void disableDirtyRequests(); /** * \see disableDirtyRequests() */ void enableDirtyRequests(); /** * Installs a filter object that will filter all the incoming projection update * requests. If the filter return true, the incoming update is dropped. * * NOTE: you cannot set filters recursively! */ void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter); /** * \see setProjectionUpdatesFilter() */ KisProjectionUpdatesFilterSP projectionUpdatesFilter() const; void refreshGraphAsync(KisNodeSP root = KisNodeSP()); void refreshGraphAsync(KisNodeSP root, const QRect &rc); void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect); /** * Triggers synchronous recomposition of the projection */ void refreshGraph(KisNodeSP root = KisNodeSP()); void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect); void initialRefreshGraph(); /** * Initiate a stack regeneration skipping the recalculation of the * filthy node's projection. * * Works exactly as pseudoFilthy->setDirty() with the only * exception that pseudoFilthy::updateProjection() will not be * called. That is used by KisRecalculateTransformMaskJob to avoid * cyclic dependencies. */ void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect); /** * Adds a spontaneous job to the updates queue. * * A spontaneous job may do some trivial tasks in the background, * like updating the outline of selection or purging unused tiles * from the existing paint devices. */ void addSpontaneousJob(KisSpontaneousJob *spontaneousJob); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks the current stroke should undo its last * action, for example, when the user presses Ctrl+Z while some * stroke is active. * * If the creator of the stroke supports undoing of intermediate * actions, it will be notified about this request and can undo * its last action. */ void requestUndoDuringStroke(); /** * This method is called by the UI (*not* by the creator of the * stroke) when it thinks current stroke should be cancelled. If * there is a running stroke that has already been detached from * its creator (ended or cancelled), it will be forcefully * cancelled and reverted. If there is an open stroke present, and * if its creator supports cancelling, it will be notified about * the request and the stroke will be cancelled */ void requestStrokeCancellation(); /** * This method requests the last stroke executed on the image to become undone. * If the stroke is not ended, or if all the Lod0 strokes are completed, the method * returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT * is returned and the caller should just wait for its completion and call global undo * instead. UNDO_OK means one unfinished stroke has been undone. */ UndoResult tryUndoUnfinishedLod0Stroke(); /** * This method is called when image or some other part of Krita * (*not* the creator of the stroke) decides that the stroke * should be ended. If the creator of the stroke supports it, it * will be notified and the stroke will be cancelled */ void requestStrokeEnd(); /** * Same as requestStrokeEnd() but is called by view manager when * the current node is changed. Use to dintinguish * sigStrokeEndRequested() and * sigStrokeEndRequestedActiveNodeFiltered() which are used by * KisNodeJugglerCompressed */ void requestStrokeEndActiveNode(); private: KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy); KisImage& operator=(const KisImage& rhs); void emitSizeChanged(); void resizeImageImpl(const QRect& newRect, bool cropLayers); void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians); void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin); void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2); void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea); void requestProjectionUpdateImpl(KisNode *node, const QRect& rect, const QRect &cropRect); friend class KisImageResizeCommand; void setSize(const QSize& size); friend class KisImageSetProjectionColorSpaceCommand; void setProjectionColorSpace(const KoColorSpace * colorSpace); friend class KisDeselectGlobalSelectionCommand; friend class KisReselectGlobalSelectionCommand; friend class KisSetGlobalSelectionCommand; friend class KisImageTest; /** * Replaces the current global selection with globalSelection. If * \p globalSelection is empty, removes the selection object, so that * \ref globalSelection() will return 0 after that. */ void setGlobalSelection(KisSelectionSP globalSelection); /** * Deselects current global selection. * \ref globalSelection() will return 0 after that. */ void deselectGlobalSelection(); /** * Reselects current deselected selection * * \see deselectGlobalSelection() */ void reselectGlobalSelection(); private: class KisImagePrivate; KisImagePrivate * m_d; }; #endif // KIS_IMAGE_H_ diff --git a/libs/image/kis_image_config.cpp b/libs/image/kis_image_config.cpp index c99542ebda..0258ccc41a 100644 --- a/libs/image/kis_image_config.cpp +++ b/libs/image/kis_image_config.cpp @@ -1,455 +1,455 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_image_config.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include "kis_global.h" #include #ifdef Q_OS_OSX #include #endif KisImageConfig::KisImageConfig(bool readOnly) : m_config( KSharedConfig::openConfig()->group(QString())), m_readOnly(readOnly) { } KisImageConfig::~KisImageConfig() { if (m_readOnly) return; if (qApp->thread() != QThread::currentThread()) { dbgKrita << "KisImageConfig: requested config synchronization from nonGUI thread! Called from" << kisBacktrace(); return; } m_config.sync(); } bool KisImageConfig::enableProgressReporting(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enableProgressReporting", true) : true; } void KisImageConfig::setEnableProgressReporting(bool value) { m_config.writeEntry("enableProgressReporting", value); } bool KisImageConfig::enablePerfLog(bool requestDefault) const { return !requestDefault ? m_config.readEntry("enablePerfLog", false) :false; } void KisImageConfig::setEnablePerfLog(bool value) { m_config.writeEntry("enablePerfLog", value); } qreal KisImageConfig::transformMaskOffBoundsReadArea() const { return m_config.readEntry("transformMaskOffBoundsReadArea", 0.5); } int KisImageConfig::updatePatchHeight() const { return m_config.readEntry("updatePatchHeight", 512); } void KisImageConfig::setUpdatePatchHeight(int value) { m_config.writeEntry("updatePatchHeight", value); } int KisImageConfig::updatePatchWidth() const { return m_config.readEntry("updatePatchWidth", 512); } void KisImageConfig::setUpdatePatchWidth(int value) { m_config.writeEntry("updatePatchWidth", value); } qreal KisImageConfig::maxCollectAlpha() const { return m_config.readEntry("maxCollectAlpha", 2.5); } qreal KisImageConfig::maxMergeAlpha() const { return m_config.readEntry("maxMergeAlpha", 1.); } qreal KisImageConfig::maxMergeCollectAlpha() const { return m_config.readEntry("maxMergeCollectAlpha", 1.5); } qreal KisImageConfig::schedulerBalancingRatio() const { /** * updates-queue-size / strokes-queue-size */ return m_config.readEntry("schedulerBalancingRatio", 100.); } void KisImageConfig::setSchedulerBalancingRatio(qreal value) { m_config.writeEntry("schedulerBalancingRatio", value); } int KisImageConfig::maxSwapSize(bool requestDefault) const { return !requestDefault ? m_config.readEntry("maxSwapSize", 4096) : 4096; // in MiB } void KisImageConfig::setMaxSwapSize(int value) { m_config.writeEntry("maxSwapSize", value); } int KisImageConfig::swapSlabSize() const { return m_config.readEntry("swapSlabSize", 64); // in MiB } void KisImageConfig::setSwapSlabSize(int value) { m_config.writeEntry("swapSlabSize", value); } int KisImageConfig::swapWindowSize() const { return m_config.readEntry("swapWindowSize", 16); // in MiB } void KisImageConfig::setSwapWindowSize(int value) { m_config.writeEntry("swapWindowSize", value); } int KisImageConfig::tilesHardLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * (1 - pp); } int KisImageConfig::tilesSoftLimit() const { qreal sp = qreal(memorySoftLimitPercent()) / 100.0; return tilesHardLimit() * sp; } int KisImageConfig::poolLimit() const { qreal hp = qreal(memoryHardLimitPercent()) / 100.0; qreal pp = qreal(memoryPoolLimitPercent()) / 100.0; return totalRAM() * hp * pp; } qreal KisImageConfig::memoryHardLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryHardLimitPercent", 50.) : 50.; } void KisImageConfig::setMemoryHardLimitPercent(qreal value) { m_config.writeEntry("memoryHardLimitPercent", value); } qreal KisImageConfig::memorySoftLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memorySoftLimitPercent", 2.) : 2.; } void KisImageConfig::setMemorySoftLimitPercent(qreal value) { m_config.writeEntry("memorySoftLimitPercent", value); } qreal KisImageConfig::memoryPoolLimitPercent(bool requestDefault) const { return !requestDefault ? m_config.readEntry("memoryPoolLimitPercent", 2.) : 2.; } void KisImageConfig::setMemoryPoolLimitPercent(qreal value) { m_config.writeEntry("memoryPoolLimitPercent", value); } QString KisImageConfig::swapDir(bool requestDefault) { QString swap = QDir::tempPath(); return !requestDefault ? m_config.readEntry("swaplocation", swap) : swap; } void KisImageConfig::setSwapDir(const QString &swapDir) { m_config.writeEntry("swaplocation", swapDir); } int KisImageConfig::numberOfOnionSkins() const { return m_config.readEntry("numberOfOnionSkins", 10); } void KisImageConfig::setNumberOfOnionSkins(int value) { m_config.writeEntry("numberOfOnionSkins", value); } int KisImageConfig::onionSkinTintFactor() const { return m_config.readEntry("onionSkinTintFactor", 192); } void KisImageConfig::setOnionSkinTintFactor(int value) { m_config.writeEntry("onionSkinTintFactor", value); } int KisImageConfig::onionSkinOpacity(int offset) const { int value = m_config.readEntry("onionSkinOpacity_" + QString::number(offset), -1); if (value < 0) { const int num = numberOfOnionSkins(); const qreal dx = qreal(qAbs(offset)) / num; value = 0.7 * exp(-pow2(dx) / 0.5) * 255; } return value; } void KisImageConfig::setOnionSkinOpacity(int offset, int value) { m_config.writeEntry("onionSkinOpacity_" + QString::number(offset), value); } bool KisImageConfig::onionSkinState(int offset) const { bool enableByDefault = (qAbs(offset) <= 2); return m_config.readEntry("onionSkinState_" + QString::number(offset), enableByDefault); } void KisImageConfig::setOnionSkinState(int offset, bool value) { m_config.writeEntry("onionSkinState_" + QString::number(offset), value); } QColor KisImageConfig::onionSkinTintColorBackward() const { return m_config.readEntry("onionSkinTintColorBackward", QColor(Qt::red)); } void KisImageConfig::setOnionSkinTintColorBackward(const QColor &value) { m_config.writeEntry("onionSkinTintColorBackward", value); } QColor KisImageConfig::onionSkinTintColorForward() const { return m_config.readEntry("oninSkinTintColorForward", QColor(Qt::green)); } void KisImageConfig::setOnionSkinTintColorForward(const QColor &value) { m_config.writeEntry("oninSkinTintColorForward", value); } bool KisImageConfig::lazyFrameCreationEnabled(bool requestDefault) const { return !requestDefault ? m_config.readEntry("lazyFrameCreationEnabled", true) : true; } void KisImageConfig::setLazyFrameCreationEnabled(bool value) { m_config.writeEntry("lazyFrameCreationEnabled", value); } #if defined Q_OS_LINUX #include #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD #include #elif defined Q_OS_WIN #include #elif defined Q_OS_OSX #include #include #endif #include int KisImageConfig::totalRAM() { // let's think that default memory size is 1000MiB int totalMemory = 1000; // MiB int error = 1; #if defined Q_OS_LINUX struct sysinfo info; error = sysinfo(&info); if(!error) { totalMemory = info.totalram * info.mem_unit / (1UL << 20); } #elif defined Q_OS_FREEBSD || defined Q_OS_NETBSD || defined Q_OS_OPENBSD u_long physmem; # if defined HW_PHYSMEM64 // NetBSD only int mib[] = {CTL_HW, HW_PHYSMEM64}; # else int mib[] = {CTL_HW, HW_PHYSMEM}; # endif size_t len = sizeof(physmem); error = sysctl(mib, 2, &physmem, &len, 0, 0); if(!error) { totalMemory = physmem >> 20; } #elif defined Q_OS_WIN MEMORYSTATUSEX status; status.dwLength = sizeof(status); error = !GlobalMemoryStatusEx(&status); if (!error) { totalMemory = status.ullTotalPhys >> 20; } // For 32 bit windows, the total memory available is at max the 2GB per process memory limit. # if defined ENV32BIT totalMemory = qMin(totalMemory, 2000); # endif #elif defined Q_OS_OSX int mib[2] = { CTL_HW, HW_MEMSIZE }; u_int namelen = sizeof(mib) / sizeof(mib[0]); uint64_t size; size_t len = sizeof(size); errno = 0; if (sysctl(mib, namelen, &size, &len, 0, 0) >= 0) { totalMemory = size >> 20; error = 0; } else { dbgKrita << "sysctl(\"hw.memsize\") raised error" << strerror(errno); } #endif if (error) { warnKrita << "Cannot get the size of your RAM. Using 1 GiB by default."; } return totalMemory; } bool KisImageConfig::showAdditionalOnionSkinsSettings(bool requestDefault) const { return !requestDefault ? m_config.readEntry("showAdditionalOnionSkinsSettings", true) : true; } void KisImageConfig::setShowAdditionalOnionSkinsSettings(bool value) { m_config.writeEntry("showAdditionalOnionSkinsSettings", value); } int KisImageConfig::defaultFrameColorLabel() const { return m_config.readEntry("defaultFrameColorLabel", 0); } void KisImageConfig::setDefaultFrameColorLabel(int label) { m_config.writeEntry("defaultFrameColorLabel", label); } KisProofingConfigurationSP KisImageConfig::defaultProofingconfiguration() { KisProofingConfiguration *proofingConfig= new KisProofingConfiguration(); proofingConfig->proofingProfile = m_config.readEntry("defaultProofingProfileName", "Chemical proof"); proofingConfig->proofingModel = m_config.readEntry("defaultProofingProfileModel", "CMYKA"); proofingConfig->proofingDepth = m_config.readEntry("defaultProofingProfileDepth", "U8"); proofingConfig->intent = (KoColorConversionTransformation::Intent)m_config.readEntry("defaultProofingProfileIntent", 3); if (m_config.readEntry("defaultProofingBlackpointCompensation", true)) { proofingConfig->conversionFlags |= KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } else { proofingConfig->conversionFlags = proofingConfig->conversionFlags & ~KoColorConversionTransformation::ConversionFlag::BlackpointCompensation; } - QColor def; - def = m_config.readEntry("defaultProofingGamutwarning", QColor(Qt::gray)); + QColor def(Qt::green); + m_config.readEntry("defaultProofingGamutwarning", def); KoColor col(KoColorSpaceRegistry::instance()->rgb8()); col.fromQColor(def); col.setOpacity(1.0); proofingConfig->warningColor = col; proofingConfig->adaptationState = (double)m_config.readEntry("defaultProofingAdaptationState", 1.0); return toQShared(proofingConfig); } void KisImageConfig::setDefaultProofingConfig(const KoColorSpace *proofingSpace, int proofingIntent, bool blackPointCompensation, KoColor warningColor, double adaptationState) { m_config.writeEntry("defaultProofingProfileName", proofingSpace->profile()->name()); m_config.writeEntry("defaultProofingProfileModel", proofingSpace->colorModelId().id()); m_config.writeEntry("defaultProofingProfileDepth", proofingSpace->colorDepthId().id()); m_config.writeEntry("defaultProofingProfileIntent", proofingIntent); m_config.writeEntry("defaultProofingBlackpointCompensation", blackPointCompensation); QColor c; - warningColor.toQColor(&c); + c = warningColor.toQColor(); m_config.writeEntry("defaultProofingGamutwarning", c); m_config.writeEntry("defaultProofingAdaptationState",adaptationState); } bool KisImageConfig::useLodForColorizeMask(bool requestDefault) const { return !requestDefault ? m_config.readEntry("useLodForColorizeMask", false) : false; } void KisImageConfig::setUseLodForColorizeMask(bool value) { m_config.writeEntry("useLodForColorizeMask", value); } diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 38a5dafb33..158488433b 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,874 +1,876 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisAbstractProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); if (rhs.m_d->layerStyle) { setLayerStyle(rhs.m_d->layerStyle->clone()); } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = m_d->image.toStrongRef(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisAbstractProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); - if (compositeOp()) { - l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp()->description()); + const KoCompositeOp * compositeOp = this->compositeOp(); + + if (compositeOp) { + l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { nodeProperties().setProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; KisNodeSP node = firstChild(); while (node) { KisLayerUtils::recursiveApplyNodes(node, [image] (KisNodeSP node) { node->setImage(image); }); node = node->nextSibling(); } } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } KisSelectionMaskSP KisLayer::selectionMask() const { KoProperties properties; properties.setProperty("active", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask->visible()) { return dynamic_cast(mask.data()); } } return KisSelectionMaskSP(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = m_d->image.toStrongRef(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); Q_FOREACH (const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { if (childCount() == 0) return false; KisNodeSP node = firstChild(); while (node) { if (node->inherits("KisEffectMask") && node->visible()) { return true; } node = node->nextSibling(); } return false; } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection.getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? m_d->layerStyleProjectionPlane : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_legacy_undo_adapter.cpp b/libs/image/kis_legacy_undo_adapter.cpp index 709989fd1d..4d6e2b8e87 100644 --- a/libs/image/kis_legacy_undo_adapter.cpp +++ b/libs/image/kis_legacy_undo_adapter.cpp @@ -1,75 +1,77 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_legacy_undo_adapter.h" #include "kis_image.h" KisLegacyUndoAdapter::KisLegacyUndoAdapter(KisUndoStore *undoStore, KisImageWSP image) : KisUndoAdapter(undoStore), m_image(image), m_macroCounter(0) { } const KUndo2Command* KisLegacyUndoAdapter::presentCommand() { return undoStore()->presentCommand(); } void KisLegacyUndoAdapter::undoLastCommand() { undoStore()->undoLastCommand(); } void KisLegacyUndoAdapter::addCommand(KUndo2Command *command) { if(!command) return; if(m_macroCounter) { undoStore()->addCommand(command); } else { + // TODO: add feedback m_image->barrierLock(); undoStore()->addCommand(command); m_image->unlock(); } } void KisLegacyUndoAdapter::beginMacro(const KUndo2MagicString& macroName) { if(!m_macroCounter) { + // TODO: add feedback m_image->barrierLock(); } m_macroCounter++; undoStore()->beginMacro(macroName); } void KisLegacyUndoAdapter::endMacro() { m_macroCounter--; if(!m_macroCounter) { m_image->unlock(); } undoStore()->endMacro(); } diff --git a/libs/image/kis_update_time_monitor.cpp b/libs/image/kis_update_time_monitor.cpp index 569dc5e779..b76ab09c2d 100644 --- a/libs/image/kis_update_time_monitor.cpp +++ b/libs/image/kis_update_time_monitor.cpp @@ -1,256 +1,258 @@ /* * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_update_time_monitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_global.h" #include "kis_image_config.h" #include Q_GLOBAL_STATIC(KisUpdateTimeMonitor, s_instance) struct StrokeTicket { StrokeTicket() : m_jobTime(0) , m_updateTime(0) {} QRegion dirtyRegion; void start() { m_timer.start(); } void jobCompleted() { m_jobTime = m_timer.restart(); } void updateCompleted() { m_updateTime = m_timer.restart(); } qint64 jobTime() const { return m_jobTime; } qint64 updateTime() const { return m_updateTime; } private: QElapsedTimer m_timer; qint64 m_jobTime; qint64 m_updateTime; }; struct Q_DECL_HIDDEN KisUpdateTimeMonitor::Private { Private() : jobsTime(0), responseTime(0), numTickets(0), numUpdates(0), mousePath(0.0), loggingEnabled(false) { loggingEnabled = KisImageConfig().enablePerfLog(); } QHash preliminaryTickets; QSet finishedTickets; qint64 jobsTime; qint64 responseTime; qint32 numTickets; qint32 numUpdates; QMutex mutex; qreal mousePath; QPointF lastMousePos; QElapsedTimer strokeTime; KisPaintOpPresetSP preset; bool loggingEnabled; }; KisUpdateTimeMonitor::KisUpdateTimeMonitor() : m_d(new Private) { if (m_d->loggingEnabled) { QDir dir; if (dir.exists("log")) { dir.remove("log"); } dir.mkdir("log"); } } KisUpdateTimeMonitor::~KisUpdateTimeMonitor() { delete m_d; } KisUpdateTimeMonitor* KisUpdateTimeMonitor::instance() { return s_instance; } void KisUpdateTimeMonitor::startStrokeMeasure() { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); m_d->jobsTime = 0; m_d->responseTime = 0; m_d->numTickets = 0; m_d->numUpdates = 0; m_d->mousePath = 0; m_d->lastMousePos = QPointF(); m_d->preset = 0; m_d->strokeTime.start(); } void KisUpdateTimeMonitor::endStrokeMeasure() { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); if(m_d->numTickets) { printValues(); } } void KisUpdateTimeMonitor::reportPaintOpPreset(KisPaintOpPresetSP preset) { if (!m_d->loggingEnabled) return; m_d->preset = preset; } void KisUpdateTimeMonitor::reportMouseMove(const QPointF &pos) { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); if (!m_d->lastMousePos.isNull()) { qreal distance = kisDistance(m_d->lastMousePos, pos); m_d->mousePath += distance; } m_d->lastMousePos = pos; } void KisUpdateTimeMonitor::printValues() { qint64 strokeTime = m_d->strokeTime.elapsed(); qreal responseTime = qreal(m_d->responseTime) / m_d->numTickets; qreal nonUpdateTime = qreal(m_d->jobsTime) / m_d->numTickets; - qreal jobsPerUpdate = qreal(m_d->numTickets) / m_d->numUpdates; + qreal jobsPerUpdate = qreal(m_d->numTickets) / m_d->numUpdates; qreal mouseSpeed = qreal(m_d->mousePath) / strokeTime; QString prefix; if (m_d->preset) { KisPaintOpPresetSP preset = m_d->preset->clone(); prefix = QString("%1.").arg(preset->name()); preset->setFilename(QString("log/%1.kpp").arg(preset->name())); preset->save(); } QFile logFile(QString("log/%1stroke.rdata").arg(prefix)); logFile.open(QIODevice::Append); QTextStream stream(&logFile); - stream << mouseSpeed << "\t" - << jobsPerUpdate << "\t" - << nonUpdateTime << "\t" - << responseTime << "\n"; + + stream << i18n("Stroke Time:") << strokeTime << "\t" + << i18n("Mouse Speed:") << QString::number( mouseSpeed, 'f', 3 ) << "\t" + << i18n("Jobs/Update:") << QString::number( jobsPerUpdate, 'f', 3 ) << "\t" + << i18n("Non Update Time:") << QString::number( nonUpdateTime, 'f', 3 ) << "\t" + << i18n("Response Time:") << responseTime << endl; // 'endl' will use the correct OS line ending logFile.close(); } void KisUpdateTimeMonitor::reportJobStarted(void *key) { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); StrokeTicket *ticket = new StrokeTicket(); ticket->start(); m_d->preliminaryTickets.insert(key, ticket); } void KisUpdateTimeMonitor::reportJobFinished(void *key, const QVector &rects) { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); StrokeTicket *ticket = m_d->preliminaryTickets.take(key); if( ticket ){ ticket->jobCompleted(); Q_FOREACH (const QRect &rect, rects) { ticket->dirtyRegion += rect; } m_d->finishedTickets.insert(ticket); } } void KisUpdateTimeMonitor::reportUpdateFinished(const QRect &rect) { if (!m_d->loggingEnabled) return; QMutexLocker locker(&m_d->mutex); Q_FOREACH (StrokeTicket *ticket, m_d->finishedTickets) { ticket->dirtyRegion -= rect; if(ticket->dirtyRegion.isEmpty()) { ticket->updateCompleted(); m_d->jobsTime += ticket->jobTime(); m_d->responseTime += ticket->jobTime() + ticket->updateTime(); m_d->numTickets++; m_d->finishedTickets.remove(ticket); delete ticket; } } m_d->numUpdates++; } diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index 38b4814e32..e8a24ea9c6 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,641 +1,641 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector colorants() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. virtual QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ - virtual QString id() const; + QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For intance "RGBA (8-bits)" or "CMYKA (16-bits)". */ - virtual QString name() const; + QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ virtual const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src * @param dst * @param dstColorSpace the colorspace to which we go to. * @param proofingSpace the proofing space. * @param numPixels the amount of pixels. * @param renderingIntent the rendering intent used for rendering. * @param proofingIntent the intent used for proofing. * @param conversionFlags the conversion flags. * @param gamutWarning the data() of a KoColor. * @param adaptationState the state of adaptation, only affects absolute colorimetric. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the different; * other transparency levels are not regarded when finding the difference. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param param the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * This function doesn't create the element but rather the , * , ... elements. It is assumed that colorElt is the * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available * at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (, , ) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/pigment/KoLabColorSpaceTraits.h b/libs/pigment/KoLabColorSpaceTraits.h index 1cc4ce0a6c..43eb0f72a7 100644 --- a/libs/pigment/KoLabColorSpaceTraits.h +++ b/libs/pigment/KoLabColorSpaceTraits.h @@ -1,109 +1,400 @@ /* * Copyright (c) 2006-2007 Cyrille Berger + * Copyright (c) 2016 L. E. Segovia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 _KO_LAB_COLORSPACE_TRAITS_H_ #define _KO_LAB_COLORSPACE_TRAITS_H_ /** * LAB traits, it provides some convenient functions to * access LAB channels through an explicit API. * * Use this class in conjonction with KoColorSpace::toLabA16 and * KoColorSpace::fromLabA16 data. * * Example: * quint8* p = KoLabU16Traits::allocate(1); * oneKoColorSpace->toLabA16(somepointertodata, p, 1); * KoLabU16Traits::setL( p, KoLabU16Traits::L(p) / 10 ); * oneKoColorSpace->fromLabA16(p, somepointertodata, 1); */ template struct KoLabTraits : public KoColorSpaceTrait<_channels_type_, 4, 3> { typedef _channels_type_ channels_type; typedef KoColorSpaceTrait<_channels_type_, 4, 3> parent; static const qint32 L_pos = 0; static const qint32 a_pos = 1; static const qint32 b_pos = 2; /** * An Lab pixel */ struct Pixel { channels_type L; channels_type a; channels_type b; channels_type alpha; }; /// @return the L component inline static channels_type L(quint8* data) { channels_type* d = parent::nativeArray(data); return d[L_pos]; } /// Set the L component inline static void setL(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[L_pos] = nv; } /// @return the a component inline static channels_type a(quint8* data) { channels_type* d = parent::nativeArray(data); return d[a_pos]; } /// Set the a component inline static void setA(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[a_pos] = nv; } /// @return the b component inline static channels_type b(quint8* data) { channels_type* d = parent::nativeArray(data); return d[b_pos]; } /// Set the a component inline static void setB(quint8* data, channels_type nv) { channels_type* d = parent::nativeArray(data); d[b_pos] = nv; } }; +//For quint* values must range from 0 to 1 - see KoColorSpaceMaths struct KoLabU8Traits : public KoLabTraits { + + static const quint32 MAX_CHANNEL_L = 100; + static const quint32 MAX_CHANNEL_AB = 255; + static const quint32 CHANNEL_AB_ZERO_OFFSET = 128; + + inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { + Q_ASSERT((int)channels.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + c = nativeArray(pixel)[i]; + switch (i) { + case L_pos: + channels[i] = ((qreal)c) / MAX_CHANNEL_L; + break; + case a_pos: + channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + break; + case b_pos: + channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + break; + case 3: + channels[i] = ((qreal)c) / UINT16_MAX; + break; + default: + channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; + break; + } + } + } + + inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case L_pos: + return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); + case a_pos: + return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + case b_pos: + return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + case 3: + return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); + default: + return QString("Error"); + } + } + + inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { + Q_ASSERT((int)values.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < channels_nb; i++) { + float b = 0; + + switch (i) { + case L_pos: + b = qBound((float)0, + (float)MAX_CHANNEL_L * values[i], + (float)MAX_CHANNEL_L); + break; + case a_pos: + case b_pos: + b = qBound((float)0, + (float)MAX_CHANNEL_AB * values[i], + (float)MAX_CHANNEL_AB); + break; + default: + b = qBound((float)KoColorSpaceMathsTraits::min, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)KoColorSpaceMathsTraits::max); + break; + } + c = (channels_type)b; + nativeArray(pixel)[i] = c; + } + } }; struct KoLabU16Traits : public KoLabTraits { + // https://github.com/mm2/Little-CMS/blob/master/src/cmspcs.c + static const quint32 MAX_CHANNEL_L = 0xFF00; + static const quint32 MAX_CHANNEL_AB = 0xFFFF; + static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000; + + inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { + Q_ASSERT((int)channels.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + c = nativeArray(pixel)[i]; + switch (i) { + case L_pos: + channels[i] = ((qreal)c) / MAX_CHANNEL_L; + break; + case a_pos: + channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + break; + case b_pos: + channels[i] = (((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB; + break; + case 3: + channels[i] = ((qreal)c) / UINT16_MAX; + break; + default: + channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; + break; + } + } + } + + inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { + if (channelIndex > parent::channels_nb) return QString("Error"); + channels_type c = nativeArray(pixel)[channelIndex]; + switch (channelIndex) { + case L_pos: + return QString().setNum(100.0 * ((qreal)c) / MAX_CHANNEL_L); + case a_pos: + return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + case b_pos: + return QString().setNum(100.0 * ((((qreal)c) - CHANNEL_AB_ZERO_OFFSET) / MAX_CHANNEL_AB)); + case 3: + return QString().setNum(100.0 * ((qreal)c) / UINT16_MAX); + default: + return QString("Error"); + } + } + + inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { + Q_ASSERT((int)values.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < channels_nb; i++) { + float b = 0; + + switch (i) { + case L_pos: + b = qBound((float)0, + (float)MAX_CHANNEL_L * values[i], + (float)MAX_CHANNEL_L); + break; + case a_pos: + case b_pos: + b = qBound((float)0, + (float)MAX_CHANNEL_AB * values[i], + (float)MAX_CHANNEL_AB); + break; + default: + b = qBound((float)KoColorSpaceMathsTraits::min, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)KoColorSpaceMathsTraits::max); + break; + } + c = (channels_type)b; + nativeArray(pixel)[i] = c; + } + } }; +// Float values are not normalised +// XXX: is it really necessary to bind them to these ranges? + #include #ifdef HAVE_OPENEXR #include struct KoLabF16Traits : public KoLabTraits { + static constexpr float MIN_CHANNEL_L = 0; + static constexpr float MAX_CHANNEL_L = 100; + static constexpr float MIN_CHANNEL_AB = -128; + static constexpr float MAX_CHANNEL_AB = +127; + + // Lab has some... particulars + // For instance, float et al. are NOT normalised + inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { + return channelValueText(pixel, channelIndex); + } + inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { + Q_ASSERT((int)channels.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + c = parent::nativeArray(pixel)[i]; + channels[i] = (qreal)c; + } + } + inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { + Q_ASSERT((int)values.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + float b = 0; + switch(i) { + case L_pos: + b = qBound((float)MIN_CHANNEL_L, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_L); + break; + case a_pos: + case b_pos: + b = qBound((float)MIN_CHANNEL_AB, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_AB); + break; + case 3: + b = qBound((float)KoColorSpaceMathsTraits::min, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)KoColorSpaceMathsTraits::max); + default: + break; + } + c = (channels_type)b; + parent::nativeArray(pixel)[i] = c; + } + } }; #endif struct KoLabF32Traits : public KoLabTraits { + static constexpr float MIN_CHANNEL_L = 0; + static constexpr float MAX_CHANNEL_L = 100; + static constexpr float MIN_CHANNEL_AB = -128; + static constexpr float MAX_CHANNEL_AB = +127; + + // Lab has some... particulars + inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { + return channelValueText(pixel, channelIndex); + } + inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { + Q_ASSERT((int)channels.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + c = parent::nativeArray(pixel)[i]; + channels[i] = (qreal)c; + } + } + inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { + Q_ASSERT((int)values.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + float b = 0; + switch(i) { + case L_pos: + b = qBound((float)MIN_CHANNEL_L, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_L); + break; + case a_pos: + case b_pos: + b = qBound((float)MIN_CHANNEL_AB, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_AB); + break; + case 3: + b = qBound((float)KoColorSpaceMathsTraits::min, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)KoColorSpaceMathsTraits::max); + default: + break; + } + c = (channels_type)b; + parent::nativeArray(pixel)[i] = c; + } + } }; struct KoLabF64Traits : public KoLabTraits { + static constexpr double MIN_CHANNEL_L = 0; + static constexpr double MAX_CHANNEL_L = 100; + static constexpr double MIN_CHANNEL_AB = -128; + static constexpr double MAX_CHANNEL_AB = +127; + + // Lab has some... particulars + inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { + return channelValueText(pixel, channelIndex); + } + inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { + Q_ASSERT((int)channels.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + c = parent::nativeArray(pixel)[i]; + channels[i] = (qreal)c; + } + } + inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { + Q_ASSERT((int)values.count() == (int)parent::channels_nb); + channels_type c; + for (uint i = 0; i < parent::channels_nb; i++) { + float b = 0; + switch(i) { + case L_pos: + b = qBound((float)MIN_CHANNEL_L, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_L); + break; + case a_pos: + case b_pos: + b = qBound((float)MIN_CHANNEL_AB, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)MAX_CHANNEL_AB); + break; + case 3: + b = qBound((float)KoColorSpaceMathsTraits::min, + (float)KoColorSpaceMathsTraits::unitValue * values[i], + (float)KoColorSpaceMathsTraits::max); + default: + break; + } + c = (channels_type)b; + parent::nativeArray(pixel)[i] = c; + } + } }; #endif diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 9b784d4e92..831a329654 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,734 +1,752 @@ - /* This file is part of the KDE project +/* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2009 Thomas Zander Copyright (C) 2012 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #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 "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplicationPrivate { public: KisApplicationPrivate() : splashScreen(0) {} - KisSplashScreen *splashScreen; + QPointer splashScreen; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash = 0) : m_splash(splash) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (hideSplash) { m_splash->hide(); } else { - m_splash->setWindowFlags(Qt::Tool); + m_splash->setWindowFlags(Qt::Dialog); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); - m_splash->setParent(qApp->activeWindow()); + m_splash->setParent(0); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } - m_splash->show(); + m_splash->activateWindow(); } } } - KisSplashScreen *m_splash; + QPointer m_splash; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new KisApplicationPrivate) , m_autosaveDialog(0) , m_mainWindow(0) , m_batchRun(false) { QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. -// setOrganizationName("krita"); + // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); -// // Extra directories to look for create resources. (Does anyone actually use that anymore?) -// KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); -// KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); -// KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); -// KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); -// KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); -// KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); -// KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); -// KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); + // // Extra directories to look for create resources. (Does anyone actually use that anymore?) + // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); + // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); + // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); + // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); + // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); + // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); + // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); + // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); } void KisApplication::loadResources() { setSplashScreenLoadingText(i18n("Loading Gradients...")); + processEvents(); KoResourceServerProvider::instance()->gradientServer(true); + // Load base resources setSplashScreenLoadingText(i18n("Loading Patterns...")); + processEvents(); KoResourceServerProvider::instance()->patternServer(true); setSplashScreenLoadingText(i18n("Loading Palettes...")); + processEvents(); KoResourceServerProvider::instance()->paletteServer(false); setSplashScreenLoadingText(i18n("Loading Brushes...")); + processEvents(); KisBrushServer::instance()->brushServer(true); // load paintop presets setSplashScreenLoadingText(i18n("Loading Paint Operations...")); + processEvents(); KisResourceServerProvider::instance()->paintOpPresetServer(true); setSplashScreenLoadingText(i18n("Loading Resource Bundles...")); + processEvents(); KisResourceServerProvider::instance()->resourceBundleServer(); } void KisApplication::loadPlugins() { KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); + processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); + processEvents(); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); + processEvents(); KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { #if defined(Q_OS_WIN) || defined (Q_OS_OSX) #ifdef ENV32BIT KisConfig cfg; if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif setSplashScreenLoadingText(i18n("Initializing Globals")); + processEvents(); initializeGlobals(args); const bool doTemplate = args.doTemplate(); const bool print = args.print(); const bool exportAs = args.exportAs(); const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); // print & exportAsPdf do user interaction ATM const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed // TODO: fix print & exportAsPdf to work without mainwindow shown const bool showmainWindow = !exportAs; // would be !batchRun; - const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH") && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME"; - if (showSplashScreen) { + const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");// && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME"; + if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); + processEvents(); addResourceTypes(); // Load all resources and tags before the plugins do that loadResources(); // Load the plugins loadPlugins(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); + processEvents(); m_mainWindow = KisPart::instance()->createMainWindow(); if (showmainWindow) { m_mainWindow->initializeGeometry(); m_mainWindow->show(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) if (!m_batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label + processEvents(); // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (m_batchRun) { continue; } if (createNewDocFromTemplate(fileName, m_mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); doc->setOutputMimeType(outputMimetype.toLatin1()); if (!doc->exportDocument(QUrl::fromLocalFile(exportFileName))) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } else if (m_mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (m_mainWindow->openDocumentInternal(QUrl::fromLocalFile(fileName), doc)) { if (print) { m_mainWindow->slotFilePrint(); nPrinted++; // TODO: trigger closing of app once printing is done } else if (exportAsPdf) { KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); if (job) connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, SLOT(slotFileQuit()), Qt::QueuedConnection); nPrinted++; } else { // Normal case, success numberOfOpenDocuments++; } } else { // .... if failed // delete doc; done by openDocument } } } } if (m_batchRun) { return nPrinted > 0; } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash - d->splashScreen->displayLinks(); - d->splashScreen->displayRecentFiles(); + if (d->splashScreen) { + d->splashScreen->displayLinks(); + d->splashScreen->displayRecentFiles(); + } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { - d->splashScreen->loadingLabel->setText(textToLoad); - d->splashScreen->repaint(); + if (d->splashScreen) { + d->splashScreen->loadingLabel->setText(textToLoad); + d->splashScreen->repaint(); + } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mw->openDocumentInternal(QUrl::fromLocalFile(filename), doc); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mainWindow->openDocumentInternal(QUrl::fromLocalFile(url), doc); } } void KisApplication::checkAutosaveFiles() { if (m_batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application m_autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (m_autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, m_autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } m_autosaveFiles = filesToRecover; } else { m_autosaveFiles.clear(); } if (m_autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (m_mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); m_mainWindow->openDocumentInternal(url, doc); } } } // cleanup delete m_autosaveDialog; m_autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (mainWindow->openDocumentInternal(templateURL, doc)) { doc->resetURL(); doc->setTitleModified(); dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 0be6549d03..84969166f2 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2394 +1,2394 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "KisDocument.h" #include "KisDocument.h" #include "kis_group_layer.h" #include "kis_icon_utils.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_resource_server_provider.h" #include "kis_signal_compressor_with_param.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include "kis_animation_exporter.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; QPointer progress; QPointer progressCancel; QMutex progressMutex; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; bool isImporting {false}; bool isExporting {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; QUrl lastExportUrl; QMap dockWidgetsMap; QMap dockWidgetVisibilityMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita.xmlgui")); setXMLFile(":/kxmlgui5/krita.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", toolbarList); setToolbarList(toolbarList); applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); #ifdef Q_OS_WIN auto w = qApp->activeWindow(); if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true); #endif QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool))); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) static_cast(window)->reloadRecentFileList(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else { QString caption( d->activeView->document()->caption() ); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } d->activeView->setWindowTitle(caption); updateCaption(caption, d->activeView->document()->isModified()); if (!d->activeView->document()->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url) { if (!QFile(url.toLocalFile()).exists()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url); } bool KisMainWindow::openDocumentInternal(const QUrl &url, KisDocument *newdoc) { if (!url.isLocalFile()) { qDebug() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } if (!newdoc) { newdoc = KisPart::instance()->createDocument(); } d->firstTime = true; connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); bool openRet = (!d->isImporting) ? newdoc->openUrl(url) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); } QStringList KisMainWindow::showOpenFileDialog() { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setCaption(d->isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; - KisDelayedSaveDialog dlg(document->image(), this); + // no busy wait for saving because it is dangerous! + KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() != QDialog::Accepted) { return false; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int))); connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray _native_format = document->nativeFormatMimeType(); QByteArray oldOutputFormat = document->outputMimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter; mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); if (!mimeFilter.contains(oldOutputFormat) && !d->isExporting) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = suggestedURL.fileName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name int c = suggestedFilename.lastIndexOf('.'); const QString ext = KisMimeDatabase::suffixesForMimeType(_native_format).first(); if (!ext.isEmpty()) { if (c < 0) suggestedFilename = suggestedFilename + "." + ext; else suggestedFilename = suggestedFilename.left(c) + "." + ext; } else { // current filename extension wrong anyway if (c > 0) { // this assumes that a . signifies an extension, not just a . suggestedFilename = suggestedFilename.left(c); } } suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; - KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument"); + KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("untitled")); if (d->isExporting && !d->lastExportUrl.isEmpty()) { dialog.setDefaultDir(d->lastExportUrl.toLocalFile()); } else { dialog.setDefaultDir(suggestedURL.toLocalFile()); } // Default to all supported file types if user is exporting, otherwise use Krita default dialog.setMimeTypeFilters(mimeFilter, QString(_native_format)); QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = _native_format; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); if (!d->isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { // // Note: // If the user is stupid enough to Export to the current URL, // we do _not_ change this operation into a Save As. Reasons // follow: // // 1. A check like "d->isExporting && oldURL == newURL" // doesn't _always_ work on case-insensitive filesystems // and inconsistent behaviour is bad. // 2. It is probably not a good idea to change document->mimeType // and friends because the next time the user File/Save's, // (not Save As) they won't be expecting that they are // using their File/Export settings // // As a bad side-effect of this, the modified flag will not // be updated and it is possible that what is currently on // their screen is not what is stored on disk (through loss // of formatting). But if you are dumb enough to change // mimetype but not the filename, then arguably, _you_ are // the "bug" :) // // - Clarence // document->setOutputMimeType(outputFormat); if (!d->isExporting) { // Save As ret = document->saveAs(newURL); if (ret) { dbgUI << "Successful Save As!"; addRecentURL(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); document->setOutputMimeType(oldOutputFormat); } } else { // Export ret = document->exportDocument(newURL); if (ret) { // a few file dialog convenience things d->lastExportUrl = newURL; d->lastExportedFormat = outputFormat; } // always restore output format document->setOutputMimeType(oldOutputFormat); } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // be sure document has the correct outputMimeType! if (d->isExporting || document->isModified()) { ret = document->save(); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { KisView *view = dynamic_cast(subwin); if (view) { KisPart::instance()->removeView(view); } } if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { openDocument(url); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "application-x-krita"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "klipper"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotFileOpen() { QStringList urls = showOpenFileDialog(); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url)); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile())); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document())) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { if(!slotFileCloseAll()) return; close(); Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) { if (mainWin != this) { if(!mainWin->slotFileCloseAll()) return; mainWin->close(); } } } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; - KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument"); + KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); document->setFileProgressProxy(); document->setFileProgressUpdater(i18n("Import frames")); KisAnimationImporter importer(document); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); document->clearFileProgressUpdater(); document->clearFileProgressProxy(); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::slotProgress(int value) { qApp->processEvents(); StdLockableWrapper wrapper(&d->progressMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return; dbgUI << "KisMainWindow::slotProgress" << value; if (value <= -1 || value >= 100) { if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progressCancel = 0; } d->firstTime = true; return; } if (d->firstTime || !d->progress) { // The statusbar might not even be created yet. // So check for that first, and create it if necessary QStatusBar *bar = findChild(); if (!bar) { statusBar()->show(); QApplication::sendPostedEvents(this, QEvent::ChildAdded); } if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); statusBar()->removeWidget(d->progressCancel); delete d->progressCancel; d->progress = 0; } d->progressCancel = new QToolButton(statusBar()); d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height()); d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop")); statusBar()->addPermanentWidget(d->progressCancel); d->progress = new QProgressBar(statusBar()); d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); d->progress->setRange(0, 100); statusBar()->addPermanentWidget(d->progress); connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled())); d->progress->show(); d->progressCancel->show(); d->firstTime = false; } if (!d->progress.isNull()) { d->progress->setValue(value); } qApp->processEvents(); } void KisMainWindow::slotProgressCanceled() { emit sigProgressCanceled(); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; d->isImporting = true; slotFileOpen(); d->isImporting = false; } void KisMainWindow::slotExportFile() { dbgUI << "slotExportFile()"; d->isExporting = true; slotFileSaveAs(); d->isExporting = false; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget()); // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } titleBar->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); //dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar; if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_OSX dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::setToolbarList(QList toolbarList) { qDeleteAll(d->toolbarList); d->toolbarList = toolbarList; } void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) { updateCaption(caption, mod); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty()) title = doc->image()->objectName(); QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString()); } else { text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString()); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg; QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } void KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Calligra LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { title = d->activeView->document()->url().fileName(); // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first(); if (title.endsWith(extension)) { title.chop(extension.length()); } } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); + KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->exportPdf = actionManager->createAction("file_export_pdf"); connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); d->importAnimation->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); + cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); - { - KisConfig cfg; - d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); - } + d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::showDockerTitleBars(bool show) { Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed)); } } KisConfig cfg; cfg.setShowDockerTitleBars(show); } void KisMainWindow::moveEvent(QMoveEvent *e) { if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) { KisConfigNotifier::instance()->notifyConfigChanged(); } } #include diff --git a/libs/ui/KisResourceBundle.cpp b/libs/ui/KisResourceBundle.cpp index b36a3f74ca..a4f9da1af4 100644 --- a/libs/ui/KisResourceBundle.cpp +++ b/libs/ui/KisResourceBundle.cpp @@ -1,989 +1,1002 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * 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 "KisResourceBundle.h" #include "KisResourceBundleManifest.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 KisResourceBundle::KisResourceBundle(QString const& fileName) : KoResource(fileName), m_bundleVersion("1") { setName(QFileInfo(fileName).baseName()); m_metadata["generator"] = "Krita (" + KritaVersionWrapper::versionString(true) + ")"; } KisResourceBundle::~KisResourceBundle() { } QString KisResourceBundle::defaultFileExtension() const { return QString(".bundle"); } bool KisResourceBundle::load() { if (filename().isEmpty()) return false; QScopedPointer resourceStore(KoStore::createStore(filename(), KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { warnKrita << "Could not open store on bundle" << filename(); m_installed = false; setValid(false); return false; } else { m_metadata.clear(); bool toRecreate = false; if (resourceStore->open("META-INF/manifest.xml")) { if (!m_manifest.load(resourceStore->device())) { warnKrita << "Could not open manifest for bundle" << filename(); return false; } resourceStore->close(); Q_FOREACH (KisResourceBundleManifest::ResourceReference ref, m_manifest.files()) { if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Bundle is broken. File" << ref.resourcePath << "is missing"; toRecreate = true; } else { resourceStore->close(); } } if(toRecreate) { warnKrita << "Due to missing files and wrong entries in the manifest, " << filename() << " will be recreated."; } } else { warnKrita << "Could not load META-INF/manifest.xml"; return false; } bool versionFound = false; if (resourceStore->open("meta.xml")) { KoXmlDocument doc; if (!doc.setContent(resourceStore->device())) { warnKrita << "Could not parse meta.xml for" << filename(); return false; } // First find the manifest:manifest node. KoXmlNode n = doc.firstChild(); for (; !n.isNull(); n = n.nextSibling()) { if (!n.isElement()) { continue; } if (n.toElement().tagName() == "meta:meta") { break; } } if (n.isNull()) { warnKrita << "Could not find manifest node for bundle" << filename(); return false; } const KoXmlElement metaElement = n.toElement(); for (n = metaElement.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isElement()) { KoXmlElement e = n.toElement(); if (e.tagName() == "meta:generator") { m_metadata.insert("generator", e.firstChild().toText().data()); } else if (e.tagName() == "dc:author") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "dc:title") { m_metadata.insert("title", e.firstChild().toText().data()); } else if (e.tagName() == "dc:description") { m_metadata.insert("description", e.firstChild().toText().data()); } else if (e.tagName() == "meta:initial-creator") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "dc:creator") { m_metadata.insert("author", e.firstChild().toText().data()); } else if (e.tagName() == "meta:creation-date") { m_metadata.insert("created", e.firstChild().toText().data()); } else if (e.tagName() == "meta:dc-date") { m_metadata.insert("updated", e.firstChild().toText().data()); } else if (e.tagName() == "meta:meta-userdefined") { if (e.attribute("meta:name") == "tag") { m_bundletags << e.attribute("meta:value"); } else { m_metadata.insert(e.attribute("meta:name"), e.attribute("meta:value")); } } else if(e.tagName() == "meta:bundle-version") { m_metadata.insert("bundle-version", e.firstChild().toText().data()); versionFound = true; } } } resourceStore->close(); } else { warnKrita << "Could not load meta.xml"; return false; } if (resourceStore->open("preview.png")) { // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = resourceStore->device()->readAll(); QBuffer buffer(&data); m_thumbnail.load(&buffer, "PNG"); resourceStore->close(); } else { warnKrita << "Could not open preview.png"; } /* * If no version is found it's an old bundle with md5 hashes to fix, or if some manifest resource entry * doesn't not correspond to a file the bundle is "broken", in both cases we need to recreate the bundle. */ - if(!versionFound) { + if (!versionFound) { m_metadata.insert("bundle-version", "1"); warnKrita << filename() << " has an old version and possibly wrong resources md5, so it will be recreated."; toRecreate = true; } - if(toRecreate) { + if (toRecreate) { recreateBundle(resourceStore); } + m_installed = true; setValid(true); setImage(m_thumbnail); } return true; } bool KisResourceBundle::loadFromDevice(QIODevice *) { return false; } bool saveResourceToStore(KoResource *resource, KoStore *store, const QString &resType) { if (!resource) { warnKrita << "No Resource"; return false; } if (!resource->valid()) { warnKrita << "Resource is not valid"; return false; } if (!store || store->bad()) { warnKrita << "No Store or Store is Bad"; return false; } QByteArray ba; QBuffer buf; QFileInfo fi(resource->filename()); if (fi.exists() && fi.isReadable()) { QFile f(resource->filename()); if (!f.open(QFile::ReadOnly)) { warnKrita << "Could not open resource" << resource->filename(); return false; } ba = f.readAll(); if (ba.size() == 0) { warnKrita << "Resource is empty" << resource->filename(); return false; } f.close(); buf.setBuffer(&ba); } else { warnKrita << "Could not find the resource " << resource->filename() << " or it isn't readable"; return false; } if (!buf.open(QBuffer::ReadOnly)) { warnKrita << "Could not open buffer"; return false; } Q_ASSERT(!store->hasFile(resType + "/" + resource->shortFilename())); if (!store->open(resType + "/" + resource->shortFilename())) { warnKrita << "Could not open file in store for resource"; return false; } bool res = (store->write(buf.data()) == buf.size()); store->close(); return res; } bool KisResourceBundle::save() { if (filename().isEmpty()) return false; addMeta("updated", QDate::currentDate().toString("dd/MM/yyyy")); QDir bundleDir = KoResourcePaths::saveLocation("data", "bundles"); bundleDir.cdUp(); QScopedPointer store(KoStore::createStore(filename(), KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip)); if (!store || store->bad()) return false; Q_FOREACH (const QString &resType, m_manifest.types()) { if (resType == "ko_gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = gradientServer->resourceByMD5(ref.md5sum); if (!res) res = gradientServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "gradients")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "ko_patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = patternServer->resourceByMD5(ref.md5sum); if (!res) res = patternServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "patterns")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_brushes") { KisBrushResourceServer* brushServer = KisBrushServer::instance()->brushServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KisBrushSP brush = brushServer->resourceByMD5(ref.md5sum); if (!brush) brush = brushServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); KoResource *res = brush.data(); if (!saveResourceToStore(res, store.data(), "brushes")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "ko_palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = paletteServer->resourceByMD5(ref.md5sum); if (!res) res = paletteServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "palettes")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KoResource *res = workspaceServer->resourceByMD5(ref.md5sum); if (!res) res = workspaceServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res, store.data(), "workspaces")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } else if (resType == "kis_paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(ref.md5sum); if (!res) res = paintoppresetServer->resourceByFilename(QFileInfo(ref.resourcePath).fileName()); if (!saveResourceToStore(res.data(), store.data(), "paintoppresets")) { if (res) { warnKrita << "Could not save resource" << resType << res->name(); } else { warnKrita << "could not find resource for" << QFileInfo(ref.resourcePath).fileName(); } } } } } if (!m_thumbnail.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); m_thumbnail.save(&buffer, "PNG"); if (!store->open("preview.png")) warnKrita << "Could not open preview.png"; if (store->write(byteArray) != buffer.size()) warnKrita << "Could not write preview.png"; store->close(); } saveManifest(store); saveMetadata(store); store->finalize(); return true; } bool KisResourceBundle::saveToDevice(QIODevice */*dev*/) const { return false; } bool KisResourceBundle::install() { QStringList md5Mismatch; if (filename().isEmpty()) { warnKrita << "Cannot install bundle: no file name" << this; return false; } QScopedPointer resourceStore(KoStore::createStore(filename(), KoStore::Read, "application/x-krita-resourcebundle", KoStore::Zip)); if (!resourceStore || resourceStore->bad()) { warnKrita << "Cannot open the resource bundle: invalid zip file?"; return false; } Q_FOREACH (const QString &resType, m_manifest.types()) { dbgResources << "Installing resource type" << resType; if (resType == "gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoAbstractGradient *res = gradientServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); KoAbstractGradient *res2 = gradientServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... gradientServer->addResource(res, false);//add it! if (!m_gradientsMd5Installed.contains(res->md5())) { m_gradientsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { gradientServer->addTag(res, tag); } gradientServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoPattern *res = patternServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); KoPattern *res2 = patternServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... patternServer->addResource(res, false);//add it! if (!m_patternsMd5Installed.contains(res->md5())) { m_patternsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { patternServer->addTag(res, tag); } patternServer->addTag(res, name()); } } } else if (resType == "brushes") { KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisBrushSP res = brushServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //find the resouce on the server KisBrushSP res2 = brushServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... brushServer->addResource(res, false);//add it! if (!m_brushesMd5Installed.contains(res->md5())) { m_brushesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { brushServer->addTag(res.data(), tag); } brushServer->addTag(res.data(), name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KoColorSet *res = paletteServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //find the resouce on the server KoColorSet *res2 = paletteServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... paletteServer->addResource(res, false);//add it! if (!m_palettesMd5Installed.contains(res->md5())) { m_palettesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { paletteServer->addTag(res, tag); } paletteServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisWorkspaceResource *res = workspaceServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } if (!res->loadFromDevice(resourceStore->device())) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name(); //the following tries to find the resource by name. KisWorkspaceResource *res2 = workspaceServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... workspaceServer->addResource(res, false);//add it! if (!m_workspacesMd5Installed.contains(res->md5())) { m_workspacesMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { workspaceServer->addTag(res, tag); } workspaceServer->addTag(res, name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } else if (resType == "paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files(resType)) { if (resourceStore->isOpen()) resourceStore->close(); dbgResources << "\tInstalling" << ref.resourcePath; KisPaintOpPresetSP res = paintoppresetServer->createResource(QString("bundle://%1:%2").arg(filename()).arg(ref.resourcePath)); if (!res) { warnKrita << "Could not create resource for" << ref.resourcePath; continue; } if (!resourceStore->open(ref.resourcePath)) { warnKrita << "Failed to open" << ref.resourcePath << "from bundle" << filename(); continue; } // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = resourceStore->device()->readAll(); QBuffer buffer(&data); if (!res->loadFromDevice(&buffer)) { warnKrita << "Failed to load" << ref.resourcePath << "from bundle" << filename(); continue; } dbgResources << "\t\tresource:" << res->name() << "File:" << res->filename(); //the following tries to find the resource by name. KisPaintOpPresetSP res2 = paintoppresetServer->resourceByName(res->name()); if (!res2) {//if it doesn't exist... paintoppresetServer->addResource(res, false);//add it! if (!m_presetsMd5Installed.contains(res->md5())){ m_presetsMd5Installed.append(res->md5()); } if (ref.md5sum!=res->md5()) { md5Mismatch.append(res->name()); } Q_FOREACH (const QString &tag, ref.tagList) { paintoppresetServer->addTag(res.data(), tag); } paintoppresetServer->addTag(res.data(), name()); } else { //warnKrita << "Didn't install" << res->name()<<"It already exists on the server"; } } } } m_installed = true; if(!md5Mismatch.isEmpty()){ QString message = i18n("The following resources had mismatching MD5 sums. They may have gotten corrupted, for example, during download."); QMessageBox bundleFeedback; bundleFeedback.setIcon(QMessageBox::Warning); Q_FOREACH (QString name, md5Mismatch) { message.append("\n"); message.append(name); } bundleFeedback.setText(message); bundleFeedback.exec(); } return true; } bool KisResourceBundle::uninstall() { - m_installed = false; + m_installed = false; + QStringList tags = getTagsList(); + tags << m_manifest.tags(); + tags << name(); KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("gradients")) { Q_FOREACH (const QByteArray md5, m_gradientsMd5Installed) { KoAbstractGradient *res = gradientServer->resourceByMD5(md5); if (res) { gradientServer->removeResourceFromServer(res); } } KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("patterns")) { Q_FOREACH (const QByteArray md5, m_patternsMd5Installed) { KoPattern *res = patternServer->resourceByMD5(md5); if (res) { patternServer->removeResourceFromServer(res); } } KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("brushes")) { Q_FOREACH (const QByteArray md5, m_brushesMd5Installed) { KisBrushSP res = brushServer->resourceByMD5(md5); if (res) { brushServer->removeResourceFromServer(res); } } KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("palettes")) { Q_FOREACH (const QByteArray md5, m_palettesMd5Installed) { KoColorSet *res = paletteServer->resourceByMD5(md5); if (res) { paletteServer->removeResourceFromServer(res); } } + KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("workspaces")) { Q_FOREACH (const QByteArray md5, m_workspacesMd5Installed) { KisWorkspaceResource *res = workspaceServer->resourceByMD5(md5); if (res) { workspaceServer->removeResourceFromServer(res); } } - KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); //Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, m_manifest.files("paintoppresets")) { Q_FOREACH (const QByteArray md5, m_presetsMd5Installed) { KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(md5); if (res) { paintoppresetServer->removeResourceFromServer(res); } } + Q_FOREACH(const QString &tag, tags) { + paintoppresetServer->tagCategoryRemoved(tag); + workspaceServer->tagCategoryRemoved(tag); + paletteServer->tagCategoryRemoved(tag); + brushServer->tagCategoryRemoved(tag); + patternServer->tagCategoryRemoved(tag); + gradientServer->tagCategoryRemoved(tag); + } + + return true; } void KisResourceBundle::addMeta(const QString &type, const QString &value) { m_metadata.insert(type, value); } const QString KisResourceBundle::getMeta(const QString &type, const QString &defaultValue) const { if (m_metadata.contains(type)) { return m_metadata[type]; } else { return defaultValue; } } void KisResourceBundle::addResource(QString fileType, QString filePath, QStringList fileTagList, const QByteArray md5sum) { m_manifest.addResource(fileType, filePath, fileTagList, md5sum); } QList KisResourceBundle::getTagsList() { return QList::fromSet(m_bundletags); } bool KisResourceBundle::isInstalled() { return m_installed; } QStringList KisResourceBundle::resourceTypes() { return m_manifest.types(); } QList KisResourceBundle::resources(const QString &resType) { QList references = m_manifest.files(resType); QList ret; Q_FOREACH (const KisResourceBundleManifest::ResourceReference &ref, references) { if (resType == "gradients") { KoResourceServer* gradientServer = KoResourceServerProvider::instance()->gradientServer(); KoResource *res = gradientServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "patterns") { KoResourceServer* patternServer = KoResourceServerProvider::instance()->patternServer(); KoResource *res = patternServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "brushes") { KisBrushResourceServer *brushServer = KisBrushServer::instance()->brushServer(); KoResource *res = brushServer->resourceByMD5(ref.md5sum).data(); if (res) ret << res; } else if (resType == "palettes") { KoResourceServer* paletteServer = KoResourceServerProvider::instance()->paletteServer(); KoResource *res = paletteServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "workspaces") { KoResourceServer< KisWorkspaceResource >* workspaceServer = KisResourceServerProvider::instance()->workspaceServer(); KoResource *res = workspaceServer->resourceByMD5(ref.md5sum); if (res) ret << res; } else if (resType == "paintoppresets") { KisPaintOpPresetResourceServer* paintoppresetServer = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP res = paintoppresetServer->resourceByMD5(ref.md5sum); if (res) ret << res.data(); } } return ret; } void KisResourceBundle::setThumbnail(QString filename) { if (QFileInfo(filename).exists()) { m_thumbnail = QImage(filename); m_thumbnail = m_thumbnail.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); Q_ASSERT(!m_thumbnail.isNull()); } else { m_thumbnail = QImage(256, 256, QImage::Format_ARGB32); QPainter gc(&m_thumbnail); gc.fillRect(0, 0, 256, 256, Qt::red); gc.end(); } } void KisResourceBundle::writeMeta(const char *metaTag, const QString &metaKey, KoXmlWriter *writer) { if (m_metadata.contains(metaKey)) { writer->startElement(metaTag); writer->addTextNode(m_metadata[metaKey].toUtf8()); writer->endElement(); } } void KisResourceBundle::writeUserDefinedMeta(const QString &metaKey, KoXmlWriter *writer) { if (m_metadata.contains(metaKey)) { writer->startElement("meta:meta-userdefined"); writer->addAttribute("meta:name", metaKey); writer->addAttribute("meta:value", m_metadata[metaKey]); writer->endElement(); } } void KisResourceBundle::setInstalled(bool install) { m_installed = install; } void KisResourceBundle::saveMetadata(QScopedPointer &store) { QBuffer buf; store->open("meta.xml"); buf.open(QBuffer::WriteOnly); KoXmlWriter metaWriter(&buf); metaWriter.startDocument("office:document-meta"); metaWriter.startElement("meta:meta"); writeMeta("meta:generator", "generator", &metaWriter); metaWriter.startElement("meta:bundle-version"); metaWriter.addTextNode(m_bundleVersion.toUtf8()); metaWriter.endElement(); writeMeta("dc:author", "author", &metaWriter); writeMeta("dc:title", "filename", &metaWriter); writeMeta("dc:description", "description", &metaWriter); writeMeta("meta:initial-creator", "author", &metaWriter); writeMeta("dc:creator", "author", &metaWriter); writeMeta("meta:creation-date", "created", &metaWriter); writeMeta("meta:dc-date", "updated", &metaWriter); writeUserDefinedMeta("email", &metaWriter); writeUserDefinedMeta("license", &metaWriter); writeUserDefinedMeta("website", &metaWriter); Q_FOREACH (const QString &tag, m_bundletags) { metaWriter.startElement("meta:meta-userdefined"); metaWriter.addAttribute("meta:name", "tag"); metaWriter.addAttribute("meta:value", tag); metaWriter.endElement(); } metaWriter.endElement(); // meta:meta metaWriter.endDocument(); buf.close(); store->write(buf.data()); store->close(); } void KisResourceBundle::saveManifest(QScopedPointer &store) { store->open("META-INF/manifest.xml"); QBuffer buf; buf.open(QBuffer::WriteOnly); m_manifest.save(&buf); buf.close(); store->write(buf.data()); store->close(); } void KisResourceBundle::recreateBundle(QScopedPointer &oldStore) { // Save a copy of the unmodified bundle, so that if anything goes bad the user doesn't lose it QFile file(filename()); file.copy(filename() + ".old"); QString newStoreName = filename() + ".tmp"; QScopedPointer store(KoStore::createStore(newStoreName, KoStore::Write, "application/x-krita-resourcebundle", KoStore::Zip)); KoHashGenerator *generator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); KisResourceBundleManifest newManifest; addMeta("updated", QDate::currentDate().toString("dd/MM/yyyy")); Q_FOREACH (KisResourceBundleManifest::ResourceReference ref, m_manifest.files()) { // Wrong manifest entry found, skip it if(!oldStore->open(ref.resourcePath)) continue; store->open(ref.resourcePath); QByteArray data = oldStore->device()->readAll(); oldStore->close(); store->write(data); store->close(); QByteArray result = generator->generateHash(data); newManifest.addResource(ref.fileTypeName, ref.resourcePath, ref.tagList, result); } m_manifest = newManifest; if (!m_thumbnail.isNull()) { QByteArray byteArray; QBuffer buffer(&byteArray); m_thumbnail.save(&buffer, "PNG"); if (!store->open("preview.png")) warnKrita << "Could not open preview.png"; if (store->write(byteArray) != buffer.size()) warnKrita << "Could not write preview.png"; store->close(); } saveManifest(store); saveMetadata(store); store->finalize(); // Remove the current bundle and then move the tmp one to be the correct one file.setFileName(filename()); file.remove(); file.setFileName(newStoreName); file.rename(filename()); } diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp index 8a595a0d40..be34b20847 100644 --- a/libs/ui/KisView.cpp +++ b/libs/ui/KisView.cpp @@ -1,938 +1,954 @@ /* * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisView.h" #include "KisView_p.h" #include #include #include #include "KoDocumentInfo.h" #include "KoPageLayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_config.h" #include "KisDocument.h" #include "kis_image_manager.h" #include "KisMainWindow.h" #include "kis_mimedata.h" #include "kis_mirror_axis.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisPrintJob.h" #include "kis_shape_controller.h" #include "kis_tool_freehand.h" #include "KisUndoStackAction.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "kis_composite_progress_proxy.h" #include "kis_statusbar.h" #include "kis_painting_assistants_decoration.h" #include "kis_progress_widget.h" #include "kis_signal_compressor.h" #include "kis_filter_manager.h" +#include "kis_file_layer.h" #include "krita_utils.h" #include "input/kis_input_manager.h" #include "KisRemoteFileFetcher.h" //static QString KisView::newObjectName() { static int s_viewIFNumber = 0; QString name; name.setNum(s_viewIFNumber++); name.prepend("view_"); return name; } bool KisView::s_firstView = true; class Q_DECL_HIDDEN KisView::Private { public: Private(KisView *_q, KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection) : actionCollection(actionCollection) , viewConverter() , canvasController(_q, actionCollection) , canvas(&viewConverter, resourceManager, _q, document->shapeController()) , zoomManager(_q, &this->viewConverter, &this->canvasController) , paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q)) , floatingMessageCompressor(100, KisSignalCompressor::POSTPONE) { } KisUndoStackAction *undo = 0; KisUndoStackAction *redo = 0; bool inOperation; //in the middle of an operation (no screen refreshing)? QPointer document; // our KisDocument QWidget *tempActiveWidget = 0; /** * Signals the document has been deleted. Can't use document==0 since this * only happens in ~QObject, and views get deleted by ~KisDocument. * XXX: either provide a better justification to do things this way, or * rework the mechanism. */ bool documentDeleted = false; KActionCollection* actionCollection; KisCoordinatesConverter viewConverter; KisCanvasController canvasController; KisCanvas2 canvas; KisZoomManager zoomManager; KisViewManager *viewManager = 0; KisNodeSP currentNode; KisPaintingAssistantsDecorationSP paintingAssistantsDecoration; bool isCurrent = false; bool showFloatingMessage = false; QPointer savedFloatingMessage; KisSignalCompressor floatingMessageCompressor; bool softProofing = false; bool gamutCheck = false; // Hmm sorry for polluting the private class with such a big inner class. // At the beginning it was a little struct :) class StatusBarItem { public: StatusBarItem(QWidget * widget, int stretch, bool permanent) : m_widget(widget), m_stretch(stretch), m_permanent(permanent), m_connected(false), m_hidden(false) {} bool operator==(const StatusBarItem& rhs) { return m_widget == rhs.m_widget; } bool operator!=(const StatusBarItem& rhs) { return m_widget != rhs.m_widget; } QWidget * widget() const { return m_widget; } void ensureItemShown(QStatusBar * sb) { Q_ASSERT(m_widget); if (!m_connected) { if (m_permanent) sb->addPermanentWidget(m_widget, m_stretch); else sb->addWidget(m_widget, m_stretch); if(!m_hidden) m_widget->show(); m_connected = true; } } void ensureItemHidden(QStatusBar * sb) { if (m_connected) { m_hidden = m_widget->isHidden(); sb->removeWidget(m_widget); m_widget->hide(); m_connected = false; } } private: QWidget * m_widget = 0; int m_stretch; bool m_permanent; bool m_connected = false; bool m_hidden = false; }; }; KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent) : QWidget(parent) , d(new Private(this, document, resourceManager, actionCollection)) { Q_ASSERT(document); connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool))); setObjectName(newObjectName()); d->document = document; setFocusPolicy(Qt::StrongFocus); d->undo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::UNDO); d->redo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::RED0); QStatusBar * sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&)), this, SLOT(slotActionStatusText(const QString&))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } d->canvas.setup(); KisConfig cfg; d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->canvasController.setDrawShadow(false); d->canvasController.setCanvasMode(KoCanvasController::Infinite); d->canvasController.setVastScrolling(cfg.vastScrolling()); d->canvasController.setCanvas(&d->canvas); d->zoomManager.setup(d->actionCollection); connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged())); setAcceptDrops(true); connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished())); connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished())); d->canvas.addDecoration(d->paintingAssistantsDecoration); d->paintingAssistantsDecoration->setVisible(true); d->showFloatingMessage = cfg.showCanvasMessages(); } KisView::~KisView() { if (d->viewManager) { KoProgressProxy *proxy = d->viewManager->statusBar()->progress()->progressProxy(); KIS_ASSERT_RECOVER_NOOP(proxy); image()->compositeProgressProxy()->removeProxy(proxy); if (d->viewManager->filterManager()->isStrokeRunning()) { d->viewManager->filterManager()->cancel(); } } KisPart::instance()->removeView(this); KoToolManager::instance()->removeCanvasController(&d->canvasController); delete d; } void KisView::notifyCurrentStateChanged(bool isCurrent) { d->isCurrent = isCurrent; if (!d->isCurrent && d->savedFloatingMessage) { d->savedFloatingMessage->removeMessage(); } KisInputManager *inputManager = globalInputManager(); if (d->isCurrent) { inputManager->attachPriorityEventFilter(&d->canvasController); } else { inputManager->detachPriorityEventFilter(&d->canvasController); } } void KisView::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->viewManager) return; if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) { if (d->savedFloatingMessage) { d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment); } else { d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment); d->savedFloatingMessage->setShowOverParent(true); d->savedFloatingMessage->setIcon(icon); connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage())); d->floatingMessageCompressor.start(); } } } bool KisView::canvasIsMirrored() const { return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored(); } void KisView::setViewManager(KisViewManager *view) { d->viewManager = view; KoToolManager::instance()->addController(&d->canvasController); KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController); dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas); if (resourceProvider()) { resourceProvider()->slotImageSizeChanged(); } if (d->viewManager && d->viewManager->nodeManager()) { d->viewManager->nodeManager()->nodesUpdated(); } connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&))); connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged())); // executed in a context of an image thread connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), SLOT(slotImageNodeAdded(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)), SLOT(slotContinueAddNode(KisNodeSP)), Qt::AutoConnection); // executed in a context of an image thread connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(slotImageNodeRemoved(KisNodeSP)), Qt::DirectConnection); // executed in a context of the gui thread connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)), SLOT(slotContinueRemoveNode(KisNodeSP)), Qt::AutoConnection); /* * WARNING: Currently we access the global progress bar in two ways: * connecting to composite progress proxy (strokes) and creating * progress updaters. The latter way should be deprecated in favour * of displaying the status of the global strokes queue */ image()->compositeProgressProxy()->addProxy(d->viewManager->statusBar()->progress()->progressProxy()); connect(d->viewManager->statusBar()->progress(), SIGNAL(sigCancellationRequested()), image(), SLOT(requestStrokeCancellation())); d->viewManager->updateGUI(); KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } KisViewManager* KisView::viewManager() const { return d->viewManager; } void KisView::slotImageNodeAdded(KisNodeSP node) { emit sigContinueAddNode(node); } void KisView::slotContinueAddNode(KisNodeSP newActiveNode) { /** * When deleting the last layer, root node got selected. We should * fix it when the first layer is added back. * * Here we basically reimplement what Qt's view/model do. But * since they are not connected, we should do it manually. */ if (!d->isCurrent && (!d->currentNode || !d->currentNode->parent())) { d->currentNode = newActiveNode; } } void KisView::slotImageNodeRemoved(KisNodeSP node) { emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node)); } void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode) { if (!d->isCurrent) { d->currentNode = newActiveNode; } } QAction *KisView::undoAction() const { return d->undo; } QAction *KisView::redoAction() const { return d->redo; } KoZoomController *KisView::zoomController() const { return d->zoomManager.zoomController(); } KisZoomManager *KisView::zoomManager() const { return &d->zoomManager; } KisCanvasController *KisView::canvasController() const { return &d->canvasController; } KisCanvasResourceProvider *KisView::resourceProvider() const { if (d->viewManager) { return d->viewManager->resourceProvider(); } return 0; } KisInputManager* KisView::globalInputManager() const { return d->viewManager ? d->viewManager->inputManager() : 0; } KisCanvas2 *KisView::canvasBase() const { return &d->canvas; } KisImageWSP KisView::image() const { if (d->document) { return d->document->image(); } return 0; } KisCoordinatesConverter *KisView::viewConverter() const { return &d->viewConverter; } void KisView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasImage() || event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node")) { event->accept(); // activate view if it should accept the drop this->setFocus(); } else { event->ignore(); } } void KisView::dropEvent(QDropEvent *event) { KisImageWSP kisimage = image(); Q_ASSERT(kisimage); QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint(); QRect imageBounds = kisimage->bounds(); QPoint pasteCenter; bool forceRecenter; if (event->keyboardModifiers() & Qt::ShiftModifier && imageBounds.contains(cursorPos)) { pasteCenter = cursorPos; forceRecenter = true; } else { pasteCenter = imageBounds.center(); forceRecenter = false; } if (event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasImage()) { KisShapeController *kritaShapeController = dynamic_cast(d->document->shapeController()); QList nodes = KisMimeData::loadNodes(event->mimeData(), imageBounds, pasteCenter, forceRecenter, kisimage, kritaShapeController); Q_FOREACH (KisNodeSP node, nodes) { if (node) { KisNodeCommandsAdapter adapter(viewManager()); if (!viewManager()->nodeManager()->activeLayer()) { adapter.addNode(node, kisimage->rootLayer() , 0); } else { adapter.addNode(node, viewManager()->nodeManager()->activeLayer()->parent(), viewManager()->nodeManager()->activeLayer()); } } } } else if (event->mimeData()->hasUrls()) { QList urls = event->mimeData()->urls(); if (urls.length() > 0) { QMenu popup; popup.setObjectName("drop_popup"); QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup); QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup); + QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup); + QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup); + QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup); QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup); QAction *cancel = new QAction(i18n("Cancel"), &popup); popup.addAction(insertAsNewLayer); + popup.addAction(insertAsNewFileLayer); popup.addAction(openInNewDocument); + popup.addAction(insertManyLayers); + popup.addAction(insertManyFileLayers); popup.addAction(openManyDocuments); insertAsNewLayer->setEnabled(image() && urls.count() == 1); + insertAsNewFileLayer->setEnabled(image() && urls.count() == 1); openInNewDocument->setEnabled(urls.count() == 1); + insertManyLayers->setEnabled(image() && urls.count() > 1); + insertManyFileLayers->setEnabled(image() && urls.count() > 1); openManyDocuments->setEnabled(urls.count() > 1); popup.addSeparator(); popup.addAction(cancel); QAction *action = popup.exec(QCursor::pos()); if (action != 0 && action != cancel) { QTemporaryFile *tmp = 0; - Q_FOREACH (QUrl url, urls) { + for (QUrl url : urls) { if (!url.isLocalFile()) { // download the file and substitute the url KisRemoteFileFetcher fetcher; tmp = new QTemporaryFile(); tmp->setAutoRemove(true); if (!fetcher.fetchFile(url, tmp)) { qDebug() << "Fetching" << url << "failed"; continue; } url = url.fromLocalFile(tmp->fileName()); } if (url.isLocalFile()) { if (action == insertAsNewLayer || action == insertManyLayers) { d->viewManager->imageManager()->importImage(url); activateWindow(); } + else if (action == insertAsNewFileLayer || action == insertManyFileLayers) { + KisNodeCommandsAdapter adapter(viewManager()); + KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(), + KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8); + adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode()); + } else { Q_ASSERT(action == openInNewDocument || action == openManyDocuments); if (mainWindow()) { mainWindow()->openDocument(url); } } } delete tmp; tmp = 0; } } } } } KisDocument *KisView::document() const { return d->document; } void KisView::setDocument(KisDocument *document) { d->document->disconnect(this); d->document = document; QStatusBar *sb = statusBar(); if (sb) { // No statusbar in e.g. konqueror connect(d->document, SIGNAL(statusBarMessage(const QString&)), this, SLOT(slotActionStatusText(const QString&))); connect(d->document, SIGNAL(clearStatusBarMessage()), this, SLOT(slotClearStatusText())); } } void KisView::setDocumentDeleted() { d->documentDeleted = true; } QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent) { Q_UNUSED(parent); QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this); printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage()); printDialog->setEnabledOptions(printJob->printDialogOptions()); return printDialog; } KisMainWindow * KisView::mainWindow() const { return dynamic_cast(window()); } QStatusBar * KisView::statusBar() const { KisMainWindow *mw = mainWindow(); return mw ? mw->statusBar() : 0; } void KisView::slotActionStatusText(const QString &text) { QStatusBar *sb = statusBar(); if (sb) sb->showMessage(text); } void KisView::slotClearStatusText() { QStatusBar *sb = statusBar(); if (sb) sb->clearMessage(); } QList KisView::createChangeUnitActions(bool addPixelUnit) { UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this); return unitActions->actions(); } void KisView::closeEvent(QCloseEvent *event) { // Check whether we're the last view int viewCount = KisPart::instance()->viewCount(document()); if (viewCount > 1) { // there are others still, so don't bother the user event->accept(); return; } if (queryClose()) { d->viewManager->removeStatusBarItem(zoomManager()->zoomActionWidget()); event->accept(); return; } event->ignore(); } bool KisView::queryClose() { if (!document()) return true; if (document()->isModified()) { QString name; if (document()->documentInfo()) { name = document()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = document()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : { bool isNative = (document()->outputMimeType() == document()->nativeFormatMimeType()); if (!viewManager()->mainWindow()->saveDocument(document(), !isNative)) return false; break; } case QMessageBox::No : document()->removeAutoSaveFiles(); document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; default : // case QMessageBox::Cancel : return false; } } return true; } void KisView::resetImageSizeAndScroll(bool changeCentering, const QPointF &oldImageStillPoint, const QPointF &newImageStillPoint) { const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter(); QPointF oldPreferredCenter = d->canvasController.preferredCenter(); /** * Calculating the still point in old coordinates depending on the * parameters given */ QPointF oldStillPoint; if (changeCentering) { oldStillPoint = converter->imageToWidget(oldImageStillPoint) + converter->documentOffset(); } else { QSize oldDocumentSize = d->canvasController.documentSize(); oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height()); } /** * Updating the document size */ QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes()); KoZoomController *zc = d->zoomManager.zoomController(); zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom()); zc->setPageSize(size); zc->setDocumentSize(size, true); /** * Calculating the still point in new coordinates depending on the * parameters given */ QPointF newStillPoint; if (changeCentering) { newStillPoint = converter->imageToWidget(newImageStillPoint) + converter->documentOffset(); } else { QSize newDocumentSize = d->canvasController.documentSize(); newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height()); } d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint); } void KisView::setCurrentNode(KisNodeSP node) { d->currentNode = node; } KisNodeSP KisView::currentNode() const { return d->currentNode; } KisLayerSP KisView::currentLayer() const { KisNodeSP node; KisMaskSP mask = currentMask(); if (mask) { node = mask->parent(); } else { node = d->currentNode; } return qobject_cast(node.data()); } KisMaskSP KisView::currentMask() const { return dynamic_cast(d->currentNode.data()); } KisSelectionSP KisView::selection() { KisLayerSP layer = currentLayer(); if (layer) return layer->selection(); // falls through to the global // selection, or 0 in the end if (image()) { return image()->globalSelection(); } return 0; } void KisView::slotSoftProofing(bool softProofing) { d->softProofing = softProofing; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Soft Proofing doesn't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (softProofing){ message = i18n("Soft Proofing turned on."); } else { message = i18n("Soft Proofing turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotSoftProofing(softProofing); } void KisView::slotGamutCheck(bool gamutCheck) { d->gamutCheck = gamutCheck; QString message; if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F")) { message = i18n("Gamut Warnings don't work in floating point."); viewManager()->showFloatingMessage(message,QIcon()); return; } if (gamutCheck){ message = i18n("Gamut Warnings turned on."); if (!d->softProofing){ message += "\n "+i18n("But Soft Proofing is still off."); } } else { message = i18n("Gamut Warnings turned off."); } viewManager()->showFloatingMessage(message,QIcon()); canvasBase()->slotGamutCheck(gamutCheck); } bool KisView::softProofing() { return d->softProofing; } bool KisView::gamutCheck() { return d->gamutCheck; } void KisView::slotLoadingFinished() { if (!document()) return; /** * Cold-start of image size/resolution signals */ slotImageResolutionChanged(); if (image()->locked()) { // If this is the first view on the image, the image will have been locked // so unlock it. image()->blockSignals(false); image()->unlock(); } canvasBase()->initializeImage(); /** * Dirty hack alert */ d->zoomManager.zoomController()->setAspectMode(true); if (viewConverter()) { viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE); } connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*))); connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*))); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF))); KisNodeSP activeNode = document()->preActivatedNode(); document()->setPreActivatedNode(0); // to make sure that we don't keep a reference to a layer the user can later delete. if (!activeNode) { activeNode = image()->rootLayer()->lastChild(); } while (activeNode && !activeNode->inherits("KisLayer")) { activeNode = activeNode->prevSibling(); } setCurrentNode(activeNode); zoomManager()->updateImageBoundsSnapping(); } void KisView::slotSavingFinished() { if (d->viewManager && d->viewManager->mainWindow()) { d->viewManager->mainWindow()->updateCaption(); } } KisPrintJob * KisView::createPrintJob() { return new KisPrintJob(image()); } void KisView::slotImageResolutionChanged() { resetImageSizeAndScroll(false); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); // update KoUnit value for the document if (resourceProvider()) { resourceProvider()->resourceManager()-> setResource(KoCanvasResourceManager::Unit, d->canvas.unit()); } } void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint) { resetImageSizeAndScroll(true, oldStillPoint, newStillPoint); zoomManager()->updateImageBoundsSnapping(); zoomManager()->updateGUI(); } diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp index e49a0f0272..80c36ef343 100644 --- a/libs/ui/KisViewManager.cpp +++ b/libs/ui/KisViewManager.cpp @@ -1,1273 +1,1304 @@ /* * This file is part of KimageShop^WKrayon^WKrita * * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 1999 Carsten Pfeiffer * 2002 Patrick Julien * 2003-2011 Boudewijn Rempt * 2004 Clarence Dang * 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 #include "KisViewManager.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 #include "input/kis_input_manager.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_canvas_controller.h" #include "canvas/kis_grid_manager.h" #include "dialogs/kis_dlg_blacklist_cleanup.h" #include "input/kis_input_profile_manager.h" #include "kis_action_manager.h" #include "kis_action.h" #include "kis_canvas_controls_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_composite_progress_proxy.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_control_frame.h" #include "kis_coordinates_converter.h" #include "KisDocument.h" #include "kis_favorite_resource_manager.h" #include "kis_filter_manager.h" #include "kis_group_layer.h" #include #include "kis_image_manager.h" #include #include "kis_mainwindow_observer.h" #include "kis_mask_manager.h" #include "kis_mimedata.h" #include "kis_mirror_manager.h" #include "kis_node_commands_adapter.h" #include "kis_node.h" #include "kis_node_manager.h" #include "kis_painting_assistants_manager.h" #include #include "kis_paintop_box.h" #include #include "KisPart.h" #include "KisPrintJob.h" #include "kis_progress_widget.h" #include "kis_resource_server_provider.h" #include "kis_selection.h" #include "kis_selection_manager.h" #include "kis_shape_controller.h" #include "kis_shape_layer.h" #include #include "kis_statusbar.h" #include #include #include "kis_tooltip_manager.h" #include #include "KisView.h" #include "kis_zoom_manager.h" #include "widgets/kis_floating_message.h" #include "kis_signal_auto_connection.h" #include "kis_script_manager.h" #include "kis_icon_utils.h" #include "kis_guides_manager.h" #include "kis_derived_resources.h" +#include "dialogs/kis_delayed_save_dialog.h" class BlockingUserInputEventFilter : public QObject { bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if(dynamic_cast(event) || dynamic_cast(event) || dynamic_cast(event)) { return true; } else { return false; } } }; class KisViewManager::KisViewManagerPrivate { public: - KisViewManagerPrivate(KisViewManager *_q, QWidget *_q_parent) + KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent) : filterManager(_q) , createTemplate(0) , saveIncremental(0) , saveIncrementalBackup(0) , openResourcesDirectory(0) , rotateCanvasRight(0) , rotateCanvasLeft(0) , resetCanvasRotation(0) , wrapAroundAction(0) , levelOfDetailAction(0) , showRulersAction(0) , rulersTrackMouseAction(0) , zoomTo100pct(0) , zoomIn(0) , zoomOut(0) , selectionManager(_q) , statusBar(_q) , controlFrame(_q, _q_parent) , nodeManager(_q) , imageManager(_q) , gridManager(_q) , canvasControlsManager(_q) , paintingAssistantsManager(_q) - , actionManager(_q) + , actionManager(_q, _actionCollection) , mainWindow(0) , showFloatingMessage(true) , currentImageView(0) , canvasResourceProvider(_q) , canvasResourceManager() , guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q) + , actionCollection(_actionCollection) , mirrorManager(_q) , inputManager(_q) , scriptManager(_q) , actionAuthor(0) { canvasResourceManager.addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisFlowResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisSizeResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter)); canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter)); canvasResourceManager.addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator)); } public: KisFilterManager filterManager; KisAction *createTemplate; KisAction *createCopy; KisAction *saveIncremental; KisAction *saveIncrementalBackup; KisAction *openResourcesDirectory; KisAction *rotateCanvasRight; KisAction *rotateCanvasLeft; KisAction *resetCanvasRotation; KisAction *wrapAroundAction; KisAction *levelOfDetailAction; KisAction *showRulersAction; KisAction *rulersTrackMouseAction; KisAction *zoomTo100pct; KisAction *zoomIn; KisAction *zoomOut; KisAction *softProof; KisAction *gamutCheck; KisSelectionManager selectionManager; KisGuidesManager guidesManager; KisStatusBar statusBar; KisControlFrame controlFrame; KisNodeManager nodeManager; KisImageManager imageManager; KisGridManager gridManager; KisCanvasControlsManager canvasControlsManager; KisPaintingAssistantsManager paintingAssistantsManager; BlockingUserInputEventFilter blockingEventFilter; KisActionManager actionManager; QMainWindow* mainWindow; QPointer savedFloatingMessage; bool showFloatingMessage; QPointer currentImageView; KisCanvasResourceProvider canvasResourceProvider; KoCanvasResourceManager canvasResourceManager; KisSignalCompressor guiUpdateCompressor; KActionCollection *actionCollection; KisMirrorManager mirrorManager; KisInputManager inputManager; KisSignalAutoConnectionsStore viewConnections; KisScriptManager scriptManager; KSelectAction *actionAuthor; // Select action for author profile. QByteArray canvasState; + + bool blockUntillOperationsFinishedImpl(KisImageSP image, bool force); }; KisViewManager::KisViewManager(QWidget *parent, KActionCollection *_actionCollection) - : d(new KisViewManagerPrivate(this, parent)) + : d(new KisViewManagerPrivate(this, _actionCollection, parent)) { d->actionCollection = _actionCollection; d->mainWindow = dynamic_cast(parent); d->canvasResourceProvider.setResourceManager(&d->canvasResourceManager); connect(&d->guiUpdateCompressor, SIGNAL(timeout()), this, SLOT(guiUpdateTimeout())); createActions(); setupManagers(); // These initialization functions must wait until KisViewManager ctor is complete. d->statusBar.setup(); d->controlFrame.setup(parent); //Check to draw scrollbars after "Canvas only mode" toggle is created. this->showHideScrollbars(); QScopedPointer dummy(new KoDummyCanvasController(actionCollection())); KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data()); QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility())); connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)), d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice))); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int))); connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), resourceProvider(), SLOT(slotNodeActivated(KisNodeSP))); connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant))); connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*))); connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions())); KisInputProfileManager::instance()->loadProfiles(); KisConfig cfg; d->showFloatingMessage = cfg.showCanvasMessages(); } KisViewManager::~KisViewManager() { KisConfig cfg; if (resourceProvider() && resourceProvider()->currentPreset()) { cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name()); cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor()); cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor()); } cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength()); delete d; } KActionCollection *KisViewManager::actionCollection() const { return d->actionCollection; } void KisViewManager::slotViewAdded(KisView *view) { d->inputManager.addTrackedCanvas(view->canvasBase()); if (viewCount() == 0) { d->statusBar.showAllStatusBarItems(); } } void KisViewManager::slotViewRemoved(KisView *view) { d->inputManager.removeTrackedCanvas(view->canvasBase()); if (viewCount() == 0) { d->statusBar.hideAllStatusBarItems(); } } void KisViewManager::setCurrentView(KisView *view) { bool first = true; if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(false); d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor)); first = false; KisDocument* doc = d->currentImageView->document(); if (doc) { doc->disconnect(this); } d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar); d->viewConnections.clear(); } // Restore the last used brush preset, color and background color. if (first) { KisConfig cfg; KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default")); KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset); if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (!preset) { preset = rserver->resources().first(); } if (preset) { paintOpBox()->restoreResource(preset.data()); } const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor foreground(Qt::black, cs); d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground)); KoColor background(Qt::white, cs); d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background)); } d->softProof->setChecked(view->softProofing()); d->gamutCheck->setChecked(view->gamutCheck()); QPointerimageView = qobject_cast(view); if (imageView) { // Wait for the async image to have loaded KisDocument* doc = view->document(); // connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF))); d->currentImageView = imageView; KisCanvasController *canvasController = dynamic_cast(d->currentImageView->canvasController()); d->viewConnections.addUniqueConnection(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), doc->image(), SLOT(requestStrokeEndActiveNode())); d->viewConnections.addUniqueConnection(d->rotateCanvasRight, SIGNAL(triggered()), canvasController, SLOT(rotateCanvasRight15())); d->viewConnections.addUniqueConnection(d->rotateCanvasLeft, SIGNAL(triggered()),canvasController, SLOT(rotateCanvasLeft15())); d->viewConnections.addUniqueConnection(d->resetCanvasRotation, SIGNAL(triggered()),canvasController, SLOT(resetCanvasRotation())); d->viewConnections.addUniqueConnection(d->wrapAroundAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleWrapAroundMode(bool))); d->wrapAroundAction->setChecked(canvasController->wrapAroundMode()); d->viewConnections.addUniqueConnection(d->levelOfDetailAction, SIGNAL(toggled(bool)), canvasController, SLOT(slotToggleLevelOfDetailMode(bool))); d->levelOfDetailAction->setChecked(canvasController->levelOfDetailMode()); d->viewConnections.addUniqueConnection(d->currentImageView->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), d->controlFrame.paintopBox(), SLOT(slotColorSpaceChanged(const KoColorSpace*))); d->viewConnections.addUniqueConnection(d->showRulersAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setShowRulers(bool))); d->viewConnections.addUniqueConnection(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), imageView->zoomManager(), SLOT(setRulersTrackMouse(bool))); d->viewConnections.addUniqueConnection(d->zoomTo100pct, SIGNAL(triggered()), imageView->zoomManager(), SLOT(zoomTo100())); d->viewConnections.addUniqueConnection(d->zoomIn, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomIn())); d->viewConnections.addUniqueConnection(d->zoomOut, SIGNAL(triggered()), imageView->zoomController()->zoomAction(), SLOT(zoomOut())); d->viewConnections.addUniqueConnection(d->softProof, SIGNAL(toggled(bool)), view, SLOT(slotSoftProofing(bool)) ); d->viewConnections.addUniqueConnection(d->gamutCheck, SIGNAL(toggled(bool)), view, SLOT(slotGamutCheck(bool)) ); imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked()); imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked()); showHideScrollbars(); } d->filterManager.setView(imageView); d->selectionManager.setView(imageView); d->guidesManager.setView(imageView); d->nodeManager.setView(imageView); d->imageManager.setView(imageView); d->canvasControlsManager.setView(imageView); d->actionManager.setView(imageView); d->gridManager.setView(imageView); d->statusBar.setView(imageView); d->paintingAssistantsManager.setView(imageView); d->mirrorManager.setView(imageView); if (d->currentImageView) { d->currentImageView->notifyCurrentStateChanged(true); d->currentImageView->canvasController()->activate(); d->currentImageView->canvasController()->setFocus(); } d->actionManager.updateGUI(); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), resourceProvider(), SLOT(slotImageSizeChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigResolutionChanged(double,double)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); d->viewConnections.addUniqueConnection( image(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(updateGUI())); d->viewConnections.addUniqueConnection( view->zoomManager()->zoomController(), SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)), resourceProvider(), SLOT(slotOnScreenResolutionChanged())); resourceProvider()->slotImageSizeChanged(); resourceProvider()->slotOnScreenResolutionChanged(); Q_EMIT viewChanged(); } KoZoomController *KisViewManager::zoomController() const { if (d->currentImageView) { return d->currentImageView->zoomController(); } return 0; } KisImageWSP KisViewManager::image() const { if (document()) { return document()->image(); } return 0; } KisCanvasResourceProvider * KisViewManager::resourceProvider() { return &d->canvasResourceProvider; } KisCanvas2 * KisViewManager::canvasBase() const { if (d && d->currentImageView) { return d->currentImageView->canvasBase(); } return 0; } QWidget* KisViewManager::canvas() const { if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) { return d->currentImageView->canvasBase()->canvasWidget(); } return 0; } KisStatusBar * KisViewManager::statusBar() const { return &d->statusBar; } void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent) { d->statusBar.addStatusBarItem(widget, stretch, permanent); } void KisViewManager::removeStatusBarItem(QWidget *widget) { d->statusBar.removeStatusBarItem(widget); } KisPaintopBox* KisViewManager::paintOpBox() const { return d->controlFrame.paintopBox(); } KoProgressUpdater* KisViewManager::createProgressUpdater(KoProgressUpdater::Mode mode) { return new KisProgressUpdater(d->statusBar.progress(), document()->progressProxy(), mode); } KisSelectionManager * KisViewManager::selectionManager() { return &d->selectionManager; } KisNodeSP KisViewManager::activeNode() { return d->nodeManager.activeNode(); } KisLayerSP KisViewManager::activeLayer() { return d->nodeManager.activeLayer(); } KisPaintDeviceSP KisViewManager::activeDevice() { return d->nodeManager.activePaintDevice(); } KisZoomManager * KisViewManager::zoomManager() { if (d->currentImageView) { return d->currentImageView->zoomManager(); } return 0; } KisFilterManager * KisViewManager::filterManager() { return &d->filterManager; } KisImageManager * KisViewManager::imageManager() { return &d->imageManager; } KisInputManager* KisViewManager::inputManager() const { return &d->inputManager; } KisSelectionSP KisViewManager::selection() { if (d->currentImageView) { return d->currentImageView->selection(); } return 0; } bool KisViewManager::selectionEditable() { KisLayerSP layer = activeLayer(); if (layer) { KoProperties properties; QList masks = layer->childNodes(QStringList("KisSelectionMask"), properties); if (masks.size() == 1) { return masks[0]->isEditable(); } } // global selection is always editable return true; } KisUndoAdapter * KisViewManager::undoAdapter() { if (!document()) return 0; KisImageWSP image = document()->image(); Q_ASSERT(image); return image->undoAdapter(); } void KisViewManager::createActions() { KisConfig cfg; d->saveIncremental = actionManager()->createAction("save_incremental_version"); connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental())); d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup"); connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup())); connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved())); d->saveIncremental->setEnabled(false); d->saveIncrementalBackup->setEnabled(false); KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger"); connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger())); d->createTemplate = actionManager()->createAction("create_template"); connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate())); d->createCopy = actionManager()->createAction("create_copy"); connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy())); d->openResourcesDirectory = actionManager()->createAction("open_resources_directory"); connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory())); d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right"); d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left"); d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation"); d->wrapAroundAction = actionManager()->createAction("wrap_around_mode"); d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode"); d->softProof = actionManager()->createAction("softProof"); d->gamutCheck = actionManager()->createAction("gamutCheck"); KisAction *tAction = actionManager()->createAction("showStatusBar"); tAction->setChecked(cfg.showStatusBar()); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool))); tAction = actionManager()->createAction("view_show_canvas_only"); tAction->setChecked(false); connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool))); //Workaround, by default has the same shortcut as mirrorCanvas KisAction *a = dynamic_cast(actionCollection()->action("format_italic")); if (a) { a->setDefaultShortcut(QKeySequence()); } a = actionManager()->createAction("edit_blacklist_cleanup"); connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup())); d->showRulersAction = actionManager()->createAction("view_ruler"); d->showRulersAction->setChecked(cfg.showRulers()); connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool))); d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse"); d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse()); connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool))); d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct"); d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, ""); d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, ""); d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this); connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &))); actionCollection()->addAction("settings_active_author", d->actionAuthor); slotUpdateAuthorProfileActions(); } void KisViewManager::setupManagers() { // Create the managers for filters, selections, layers etc. // XXX: When the currentlayer changes, call updateGUI on all // managers d->filterManager.setup(actionCollection(), actionManager()); d->selectionManager.setup(actionManager()); d->guidesManager.setup(actionManager()); d->nodeManager.setup(actionCollection(), actionManager()); d->imageManager.setup(actionManager()); d->gridManager.setup(actionManager()); d->paintingAssistantsManager.setup(actionManager()); d->canvasControlsManager.setup(actionManager()); d->mirrorManager.setup(actionCollection()); d->scriptManager.setup(actionCollection(), actionManager()); } void KisViewManager::updateGUI() { d->guiUpdateCompressor.start(); } void KisViewManager::slotBlacklistCleanup() { KisDlgBlacklistCleanup dialog; dialog.exec(); } KisNodeManager * KisViewManager::nodeManager() const { return &d->nodeManager; } KisActionManager* KisViewManager::actionManager() const { return &d->actionManager; } KisGridManager * KisViewManager::gridManager() const { return &d->gridManager; } KisGuidesManager * KisViewManager::guidesManager() const { return &d->guidesManager; } KisPaintingAssistantsManager* KisViewManager::paintingAssistantsManager() const { return &d->paintingAssistantsManager; } KisDocument *KisViewManager::document() const { if (d->currentImageView && d->currentImageView->document()) { return d->currentImageView->document(); } return 0; } KisScriptManager *KisViewManager::scriptManager() const { return &d->scriptManager; } int KisViewManager::viewCount() const { KisMainWindow *mw = qobject_cast(d->mainWindow); if (mw) { return mw->viewCount(); } return 0; } +bool KisViewManager::KisViewManagerPrivate::blockUntillOperationsFinishedImpl(KisImageSP image, bool force) +{ + const int busyWaitDelay = 1000; + KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow); + dialog.blockIfImageIsBusy(); + + return dialog.result() == QDialog::Accepted; +} + + +bool KisViewManager::blockUntillOperationsFinished(KisImageSP image) +{ + return d->blockUntillOperationsFinishedImpl(image, false); +} + +void KisViewManager::blockUntillOperationsFinishedForced(KisImageSP image) +{ + d->blockUntillOperationsFinishedImpl(image, true); +} + void KisViewManager::slotCreateTemplate() { if (!document()) return; KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow()); } void KisViewManager::slotCreateCopy() { if (!document()) return; KisDocument *doc = KisPart::instance()->createDocument(); QString name = document()->documentInfo()->aboutInfo("name"); if (name.isEmpty()) { name = document()->url().toLocalFile(); } name = i18n("%1 (Copy)", name); doc->documentInfo()->setAboutInfo("title", name); KisImageWSP image = document()->image(); KisImageSP newImage = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->colorSpace(), name); newImage->setRootLayer(dynamic_cast(image->rootLayer()->clone().data())); doc->setCurrentImage(newImage); KisPart::instance()->addDocument(doc); KisMainWindow *mw = qobject_cast(d->mainWindow); mw->addViewAndNotifyLoadingCompleted(doc); } QMainWindow* KisViewManager::qtMainWindow() const { if (d->mainWindow) return d->mainWindow; //Fallback for when we have not yet set the main window. QMainWindow* w = qobject_cast(qApp->activeWindow()); if(w) return w; return mainWindow(); } void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow) { d->mainWindow = newMainWindow; } void KisViewManager::slotDocumentSaved() { d->saveIncremental->setEnabled(true); d->saveIncrementalBackup->setEnabled(true); } void KisViewManager::slotSaveIncremental() { if (!document()) return; bool foundVersion; bool fileAlreadyExists; bool isBackup; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // Find current version filenames // v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well // Considering our incremental version and backup scheme, format is filename_001~001.ext QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); foundVersion = matches.at(0).isEmpty() ? false : true; // Ensure compatibility with Save Incremental Backup // If this regex is not kept separate, the entire algorithm needs modification; // It's simpler to just add this. QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]"); regexAux.indexIn(fileName); // Perform the search QStringList matchesAux = regexAux.capturedTexts(); isBackup = matchesAux.at(0).isEmpty() ? false : true; // If the filename has a version, prepare it for incrementation if (foundVersion) { version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "_" } else { // ...else, simply add a version to it so the next loop works QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(fileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(version); extensionPlusVersion.prepend("_"); fileName.replace(regex2, extensionPlusVersion); } // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("_"); if (!letter.isNull()) newVersion.append(letter); if (isBackup) { newVersion.append("~"); } else { newVersion.append("."); } fileName.replace(regex, newVersion); fileAlreadyExists = QFile(fileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental version"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } document()->setFileBatchMode(true); document()->saveAs(QUrl::fromUserInput(fileName)); document()->setFileBatchMode(false); if (mainWindow()) { mainWindow()->updateCaption(); } } void KisViewManager::slotSaveIncrementalBackup() { if (!document()) return; bool workingOnBackup; bool fileAlreadyExists; QString version = "000"; QString newVersion; QString letter; QString fileName = document()->localFilePath(); // First, discover if working on a backup file, or a normal file QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]"); regex.indexIn(fileName); // Perform the search QStringList matches = regex.capturedTexts(); workingOnBackup = matches.at(0).isEmpty() ? false : true; if (workingOnBackup) { // Try to save incremental version (of backup), use letter for alt versions version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches if (version.contains(QRegExp("[a-z]"))) { version.chop(1); // Trim "." letter = version.right(1); // Save letter version.chop(1); // Trim letter } else { version.chop(1); // Trim "." } version.remove(0, 1); // Trim "~" // Prepare the base for new version filename int intVersion = version.toInt(0); ++intVersion; QString baseNewVersion = QString::number(intVersion); QString backupFileName = document()->localFilePath(); while (baseNewVersion.length() < version.length()) { baseNewVersion.prepend("0"); } // Check if the file exists under the new name and search until options are exhausted (test appending a to z) do { newVersion = baseNewVersion; newVersion.prepend("~"); if (!letter.isNull()) newVersion.append(letter); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { if (!letter.isNull()) { char letterCh = letter.at(0).toLatin1(); ++letterCh; letter = QString(QChar(letterCh)); } else { letter = 'a'; } } } while (fileAlreadyExists && letter != "{"); // x, y, z, {... if (letter == "{") { QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number")); return; } QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName)); if (mainWindow()) mainWindow()->updateCaption(); } else { // if NOT working on a backup... // Navigate directory searching for latest backup version, ignore letters const quint8 HARDCODED_DIGIT_COUNT = 3; QString baseNewVersion = "000"; QString backupFileName = document()->localFilePath(); QRegExp regex2("[.][a-z]{2,4}$"); // Heuristic to find file extension regex2.indexIn(backupFileName); QStringList matches2 = regex2.capturedTexts(); QString extensionPlusVersion = matches2.at(0); extensionPlusVersion.prepend(baseNewVersion); extensionPlusVersion.prepend("~"); backupFileName.replace(regex2, extensionPlusVersion); // Save version with 1 number higher than the highest version found ignoring letters do { newVersion = baseNewVersion; newVersion.prepend("~"); newVersion.append("."); backupFileName.replace(regex, newVersion); fileAlreadyExists = QFile(backupFileName).exists(); if (fileAlreadyExists) { // Prepare the base for new version filename, increment by 1 int intVersion = baseNewVersion.toInt(0); ++intVersion; baseNewVersion = QString::number(intVersion); while (baseNewVersion.length() < HARDCODED_DIGIT_COUNT) { baseNewVersion.prepend("0"); } } } while (fileAlreadyExists); // Save both as backup and on current file for interapplication workflow document()->setFileBatchMode(true); QFile::copy(fileName, backupFileName); document()->saveAs(QUrl::fromUserInput(fileName)); document()->setFileBatchMode(false); if (mainWindow()) mainWindow()->updateCaption(); } } void KisViewManager::disableControls() { // prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel // this is for Bug 250944 // the solution blocks all wheel, mouse and key event, while dragging with the freehand tool // see KisToolFreehand::initPaint() and endPaint() d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->installEventFilter(&d->blockingEventFilter); } } void KisViewManager::enableControls() { d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter); Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) { child->removeEventFilter(&d->blockingEventFilter); } } void KisViewManager::showStatusBar(bool toggled) { KisMainWindow *mw = mainWindow(); if(mw && mw->statusBar()) { mw->statusBar()->setVisible(toggled); KisConfig cfg; cfg.setShowStatusBar(toggled); } } void KisViewManager::switchCanvasOnly(bool toggled) { KisConfig cfg; KisMainWindow* main = mainWindow(); if(!main) { dbgUI << "Unable to switch to canvas-only mode, main window not found"; return; } if (toggled) { d->canvasState = qtMainWindow()->saveState(); } if (cfg.hideStatusbarFullscreen()) { if (main->statusBar()) { if (!toggled) { if (main->statusBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->statusBar()->property("wasvisible").toBool()) { main->statusBar()->setVisible(true); } } } else { main->statusBar()->setProperty("wasvisible", main->statusBar()->isVisible()); main->statusBar()->setVisible(false); } } } if (cfg.hideDockersFullscreen()) { KisAction* action = qobject_cast(main->actionCollection()->action("view_toggledockers")); - action->setCheckable(true); - if (action && action->isChecked() == toggled) { - action->setChecked(!toggled); + if (action) { + action->setCheckable(true); + if (toggled) { + if (action->isChecked()) { + cfg.setShowDockers(action->isChecked()); + action->setChecked(false); + } else { + cfg.setShowDockers(false); + } + } else { + action->setChecked(cfg.showDockers()); + } } } if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) { if(toggled) { main->setWindowState( main->windowState() | Qt::WindowFullScreen); } else { main->setWindowState( main->windowState() & ~Qt::WindowFullScreen); } } if (cfg.hideMenuFullscreen()) { if (!toggled) { if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) { if (main->menuBar()->property("wasvisible").toBool()) { main->menuBar()->setVisible(true); } } } else { main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible()); main->menuBar()->setVisible(false); } } if (cfg.hideToolbarFullscreen()) { QList toolBars = main->findChildren(); Q_FOREACH (QToolBar* toolbar, toolBars) { if (!toggled) { if (toolbar->dynamicPropertyNames().contains("wasvisible")) { if (toolbar->property("wasvisible").toBool()) { toolbar->setVisible(true); } } } else { toolbar->setProperty("wasvisible", toolbar->isVisible()); toolbar->setVisible(false); } } } showHideScrollbars(); if (toggled) { // show a fading heads-up display about the shortcut to go back showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.", actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon()); } else { main->restoreState(d->canvasState); } } void KisViewManager::toggleTabletLogger() { d->inputManager.toggleTabletLogger(); } void KisViewManager::openResourcesDirectory() { QString dir = KoResourcePaths::locateLocal("data", ""); QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); } void KisViewManager::updateIcons() { if (mainWindow()) { QList dockers = mainWindow()->dockWidgets(); Q_FOREACH (QDockWidget* dock, dockers) { dbgKrita << "name " << dock->objectName(); KoDockWidgetTitleBar* titlebar = dynamic_cast(dock->titleBarWidget()); if (titlebar) { titlebar->updateIcons(); } QObjectList objects; objects.append(dock); while (!objects.isEmpty()) { QObject* object = objects.takeFirst(); objects.append(object->children()); KisIconUtils::updateIconCommon(object); } } } } void KisViewManager::initializeStatusBarVisibility() { KisConfig cfg; d->mainWindow->statusBar()->setVisible(cfg.showStatusBar()); } void KisViewManager::guiUpdateTimeout() { d->nodeManager.updateGUI(); d->selectionManager.updateGUI(); d->filterManager.updateGUI(); if (zoomManager()) { zoomManager()->updateGUI(); } d->gridManager.updateGUI(); d->actionManager.updateGUI(); } void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment) { if (!d->currentImageView) return; d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment); emit floatingMessageRequested(message, icon.name()); } KisMainWindow *KisViewManager::mainWindow() const { return qobject_cast(d->mainWindow); } void KisViewManager::showHideScrollbars() { if (!d->currentImageView) return; if (!d->currentImageView->canvasController()) return; KisConfig cfg; bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked(); if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } else { d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } void KisViewManager::slotSaveShowRulersState(bool value) { KisConfig cfg; cfg.setShowRulers(value); } void KisViewManager::slotSaveRulersTrackMouseState(bool value) { KisConfig cfg; cfg.setRulersTrackMouse(value); } void KisViewManager::setShowFloatingMessage(bool show) { d->showFloatingMessage = show; } void KisViewManager::changeAuthorProfile(const QString &profileName) { KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); if (profileName.isEmpty()) { appAuthorGroup.writeEntry("active-profile", ""); } else if (profileName == i18nc("choice for author profile", "Anonymous")) { appAuthorGroup.writeEntry("active-profile", "anonymous"); } else { appAuthorGroup.writeEntry("active-profile", profileName); } appAuthorGroup.sync(); Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) { doc->documentInfo()->updateParameters(); } } void KisViewManager::slotUpdateAuthorProfileActions() { Q_ASSERT(d->actionAuthor); if (!d->actionAuthor) { return; } d->actionAuthor->clear(); d->actionAuthor->addAction(i18n("Default Author Profile")); d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous")); KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); Q_FOREACH (const QString &profile , profiles) { d->actionAuthor->addAction(profile); } KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author"); QString profileName = appAuthorGroup.readEntry("active-profile", ""); if (profileName == "anonymous") { d->actionAuthor->setCurrentItem(1); } else if (profiles.contains(profileName)) { d->actionAuthor->setCurrentAction(profileName); } else { d->actionAuthor->setCurrentItem(0); } } - - diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h index 55d7ac8287..2264ebb37f 100644 --- a/libs/ui/KisViewManager.h +++ b/libs/ui/KisViewManager.h @@ -1,254 +1,272 @@ /* * Copyright (c) 2006 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_GUI_CLIENT_H #define KIS_GUI_CLIENT_H #include #include #include #include #include #include #include #include #include "kis_floating_message.h" class QPoint; class KisView; class KisCanvas2; class KisCanvasResourceProvider; class KisDocument; class KisFilterManager; class KisGridManager; class KisGuidesManager; class KisImageManager; class KisNodeManager; class KisPaintingAssistantsManager; class KisPaintopBox; class KisSelectionManager; class KisStatusBar; class KisUndoAdapter; class KisZoomManager; class KisPaintopBox; class KisActionManager; class KisScriptManager; class KisInputManager; /** * Krita view class * * Following the broad model-view-controller idea this class shows you one view on the document. * There can be multiple views of the same document each in with independent settings for viewMode and zoom etc. */ class KRITAUI_EXPORT KisViewManager : public QObject { Q_OBJECT public: /** * Construct a new view on the krita document. * @param document the document we show. * @param parent a parent widget we show ourselves in. */ KisViewManager(QWidget *parent, KActionCollection *actionCollection); virtual ~KisViewManager(); /** * Retrieves the entire action collection. */ virtual KActionCollection* actionCollection() const; public: // Krita specific interfaces void setCurrentView(KisView *view); /// Return the image this view is displaying KisImageWSP image() const; KoZoomController *zoomController() const; /// The resource provider contains all per-view settings, such as /// current color, current paint op etc. KisCanvasResourceProvider * resourceProvider(); /// Return the canvasbase class KisCanvas2 * canvasBase() const; /// Return the actual widget that is displaying the current image QWidget* canvas() const; /// Return the wrapper class around the statusbar KisStatusBar * statusBar() const; /** * This adds a widget to the statusbar for this view. * If you use this method instead of using statusBar() directly, * KisView will take care of removing the items when the view GUI is deactivated * and readding them when it is reactivated. * The parameters are the same as QStatusBar::addWidget(). */ void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false); /** * Remove a widget from the statusbar for this view. */ void removeStatusBarItem(QWidget * widget); KisPaintopBox* paintOpBox() const; /// create a new progress updater KoProgressUpdater *createProgressUpdater(KoProgressUpdater::Mode mode = KoProgressUpdater::Threaded); /// The selection manager handles everything action related to /// selections. KisSelectionManager *selectionManager(); /// The node manager handles everything about nodes KisNodeManager *nodeManager() const; KisActionManager *actionManager() const; /** * Convenience method to get at the active node, which may be * a layer or a mask or a selection */ KisNodeSP activeNode(); /// Convenience method to get at the active layer KisLayerSP activeLayer(); /// Convenience method to get at the active paint device KisPaintDeviceSP activeDevice(); /// The filtermanager handles everything action-related to filters KisFilterManager *filterManager(); /// The image manager handles everything action-related to the /// current image KisImageManager *imageManager(); /// Filters events and sends them to canvas actions KisInputManager *inputManager() const; /// Convenience method to get at the active selection (the /// selection of the current layer, or, if that does not exist, /// the global selection. KisSelectionSP selection(); /// Checks if the current global or local selection is editable bool selectionEditable(); /// The undo adapter is used to add commands to the undo stack KisUndoAdapter *undoAdapter(); KisDocument *document() const; KisScriptManager *scriptManager() const; int viewCount() const; + /** + * @brief blockUntillOperationsFinished blocks the GUI of the application until execution + * of actions on \p image is finished + * @param image the image which we should wait for + * @return true if the image has finished execution of the actions, false if + * the user cancelled operation + */ + bool blockUntillOperationsFinished(KisImageSP image); + + + /** + * @brief blockUntillOperationsFinished blocks the GUI of the application until execution + * of actions on \p image is finished. Does *not* provide a "Cancel" button. So the + * user is forced to wait. + * @param image the image which we should wait for + */ + void blockUntillOperationsFinishedForced(KisImageSP image); + public: KisGridManager * gridManager() const; KisGuidesManager * guidesManager() const; KisPaintingAssistantsManager* paintingAssistantsManager() const; /// disable and enable toolbar controls. used for disabling them during painting. void enableControls(); void disableControls(); /// shows a floating message in the top right corner of the canvas void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500, KisFloatingMessage::Priority priority = KisFloatingMessage::Medium, int alignment = Qt::AlignCenter | Qt::TextWordWrap); /// @return the KoMaindow this view is in, or 0 KisMainWindow *mainWindow() const; /// The QMainWindow associated with this view. This is most likely going to be shell(), but /// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow. /// This can be checked by qobject_casting to KisMainWindow to check the difference. QMainWindow* qtMainWindow() const; /// The mainWindow function will return the shell() value, unless this function is called /// with a non-null value. To make it return shell() again, simply pass null to this function. void setQtMainWindow(QMainWindow* newMainWindow); public Q_SLOTS: void switchCanvasOnly(bool toggled); void setShowFloatingMessage(bool show); void showHideScrollbars(); /// Visit all managers to update gui elements, e.g. enable / disable actions. /// This is heavy-duty call, so it uses a compressor. void updateGUI(); /// Update the style of all the icons void updateIcons(); void slotViewAdded(KisView *view); void slotViewRemoved(KisView *view); Q_SIGNALS: void floatingMessageRequested(const QString &message, const QString &iconName); /** * @brief viewChanged * sent out when the view has changed. */ void viewChanged(); private Q_SLOTS: void slotBlacklistCleanup(); void slotCreateTemplate(); void slotCreateCopy(); void slotDocumentSaved(); void slotSaveIncremental(); void slotSaveIncrementalBackup(); void showStatusBar(bool toggled); void toggleTabletLogger(); void openResourcesDirectory(); void initializeStatusBarVisibility(); void guiUpdateTimeout(); void changeAuthorProfile(const QString &profileName); void slotUpdateAuthorProfileActions(); void slotSaveShowRulersState(bool value); void slotSaveRulersTrackMouseState(bool value); private: void createActions(); void setupManagers(); /// The zoommanager handles everything action-related to zooming KisZoomManager * zoomManager(); private: class KisViewManagerPrivate; KisViewManagerPrivate * const d; }; #endif diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp index 2bf683397b..2000e51eb8 100644 --- a/libs/ui/actions/kis_selection_action_factories.cpp +++ b/libs/ui/actions/kis_selection_action_factories.cpp @@ -1,640 +1,641 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_action_factories.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_pixel_selection.h" #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "kis_fill_painter.h" #include "kis_transaction.h" #include "kis_iterator_ng.h" #include "kis_processing_applicator.h" #include "kis_group_layer.h" #include "commands/kis_selection_commands.h" #include "commands/kis_image_layer_add_command.h" #include "kis_tool_proxy.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_selection_manager.h" #include "kis_transaction_based_command.h" #include "kis_selection_filters.h" #include "kis_shape_selection.h" #include "KisPart.h" #include "kis_shape_layer.h" #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_figure_painting_tool_helper.h" namespace ActionHelper { void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false) { KisImageWSP image = view->image(); if (!image) return; KisSelectionSP selection = view->selection(); QRect rc = (selection) ? selection->selectedExactRect() : image->bounds(); KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace()); Q_CHECK_PTR(clip); const KoColorSpace *cs = clip->colorSpace(); // TODO if the source is linked... copy from all linked layers?!? // Copy image data KisPainter::copyAreaOptimized(QPoint(), device, clip, rc); if (selection) { // Apply selection mask. KisPaintDeviceSP selectionProjection = selection->projection(); KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width()); KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width()); const KoColorSpace *selCs = selection->projection()->colorSpace(); for (qint32 y = 0; y < rc.height(); y++) { for (qint32 x = 0; x < rc.width(); x++) { /** * Sharp method is an exact reverse of COMPOSITE_OVER * so if you cover the cut/copied piece over its source * you get an exactly the same image without any seams */ if (makeSharpClip) { qreal dstAlpha = cs->opacityF(layerIt->rawData()); qreal sel = selCs->opacityF(selectionIt->oldRawData()); qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha); float mask = newAlpha / dstAlpha; cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1); } else { cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1); } layerIt->nextPixel(); selectionIt->nextPixel(); } layerIt->nextRow(); selectionIt->nextRow(); } } KisClipboard::instance()->setClip(clip, rc.topLeft()); } } void KisSelectAllActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All")); if (!image->globalSelection()) { ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } struct SelectAll : public KisTransactionBasedCommand { SelectAll(KisImageSP image) : m_image(image) {} KisImageSP m_image; KUndo2Command* paint() override { KisSelectionSP selection = m_image->globalSelection(); KisSelectionTransaction transaction(selection->pixelSelection()); selection->pixelSelection()->select(m_image->bounds()); return transaction.endAndTake(); } }; ap->applyCommand(new SelectAll(image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisDeselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisDeselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisReselectActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KUndo2Command *cmd = new KisReselectGlobalSelectionCommand(image); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view) { KisNodeSP node = view->activeNode(); if (!node || !node->hasEditablePaintDevice()) return; KisSelectionSP selection = view->selection(); QRect selectedRect = selection ? selection->selectedRect() : view->image()->bounds(); Q_UNUSED(selectedRect); KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice(); Q_UNUSED(filled); bool usePattern = false; bool useBgColor = false; if (fillSource.contains("pattern")) { usePattern = true; } else if (fillSource.contains("bg")) { useBgColor = true; } KisProcessingApplicator applicator(view->image(), node, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Flood Fill Layer")); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(view->image(), node, view->resourceProvider()->resourceManager()); if (!fillSource.contains("opacity")) { resources->setOpacity(1.0); } KisProcessingVisitorSP visitor = new FillProcessingVisitor(QPoint(0, 0), // start position selection, resources, false, // fast mode usePattern, true, // fill only selection, 0, // feathering radius 0, // sizemod 80, // threshold, false, // unmerged useBgColor); applicator.applyVisitor(visitor, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } void KisClearActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Clear action" view->canvasBase()->toolProxy()->deleteSelection(); } void KisImageResizeToSelectionActionFactory::run(KisViewManager *view) { // XXX: "Add saving of XML data for Image Resize To Selection action" KisSelectionSP selection = view->selection(); if (!selection) return; view->image()->cropImage(selection->selectedExactRect()); } void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view) { KisImageSP image = view->image(); if (!image) return; bool haveShapesSelected = view->selectionManager()->haveShapesSelected(); if (haveShapesSelected) { // XXX: "Add saving of XML data for Cut/Copy of shapes" KisImageBarrierLocker locker(image); if (willCut) { view->canvasBase()->toolProxy()->cut(); } else { view->canvasBase()->toolProxy()->copy(); } } else { KisNodeSP node = view->activeNode(); if (!node) return; KisSelectionSP selection = view->selection(); if (selection.isNull()) return; { KisImageBarrierLocker locker(image); KisPaintDeviceSP dev = node->paintDevice(); if (!dev) { dev = node->projection(); } if (!dev) { view->showFloatingMessage( i18nc("floating message when cannot copy from a node", "Cannot copy pixels from this type of layer "), QIcon(), 3000, KisFloatingMessage::Medium); return; } if (dev->exactBounds().isEmpty()) { view->showFloatingMessage( i18nc("floating message when copying empty selection", "Selection is empty: no pixels were copied "), QIcon(), 3000, KisFloatingMessage::Medium); return; } ActionHelper::copyFromDevice(view, dev, makeSharpClip); } if (willCut) { KUndo2Command *command = 0; if (willCut && node->hasEditablePaintDevice()) { struct ClearSelection : public KisTransactionBasedCommand { ClearSelection(KisNodeSP node, KisSelectionSP sel) : m_node(node), m_sel(sel) {} KisNodeSP m_node; KisSelectionSP m_sel; KUndo2Command* paint() override { KisSelectionSP cutSelection = m_sel; // Shrinking the cutting area was previously used // for getting seamless cut-paste. Now we use makeSharpClip // instead. // QRect originalRect = cutSelection->selectedExactRect(); // static const int preciseSelectionThreshold = 16; // // if (originalRect.width() > preciseSelectionThreshold || // originalRect.height() > preciseSelectionThreshold) { // cutSelection = new KisSelection(*m_sel); // delete cutSelection->flatten(); // // KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false); // // QRect processingRect = filter->changeRect(originalRect); // filter->process(cutSelection->pixelSelection(), processingRect); // } KisTransaction transaction(m_node->paintDevice()); m_node->paintDevice()->clearSelection(cutSelection); m_node->setDirty(cutSelection->selectedRect()); return transaction.endAndTake(); } }; command = new ClearSelection(node, selection); } KUndo2MagicString actionName = willCut ? kundo2_i18n("Cut") : kundo2_i18n("Copy"); KisProcessingApplicator *ap = beginAction(view, actionName); if (command) { ap->applyCommand(command, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } KisOperationConfiguration config(id()); config.setProperty("will-cut", willCut); endAction(ap, config.toXML()); } } } void KisCopyMergedActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; + if (!view->blockUntillOperationsFinished(image)) return; image->barrierLock(); KisPaintDeviceSP dev = image->root()->projection(); ActionHelper::copyFromDevice(view, dev); image->unlock(); KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged")); endAction(ap, KisOperationConfiguration(id()).toXML()); } void KisPasteActionFactory::run(KisViewManager *view) { KisImageWSP image = view->image(); if (!image) return; KisPaintDeviceSP clip = KisClipboard::instance()->clip(image->bounds(), true); if (clip) { KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip); KisNodeSP aboveNode = view->activeLayer(); KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root(); KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode); KisProcessingApplicator *ap = beginAction(view, cmd->text()); ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); endAction(ap, KisOperationConfiguration(id()).toXML()); } else { // XXX: "Add saving of XML data for Paste of shapes" view->canvasBase()->toolProxy()->paste(); } } void KisPasteNewActionFactory::run(KisViewManager *viewManager) { Q_UNUSED(viewManager); KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true); if (!clip) return; QRect rect = clip->exactBounds(); if (rect.isEmpty()) return; KisDocument *doc = KisPart::instance()->createDocument(); KisImageSP image = new KisImage(doc->createUndoStore(), rect.width(), rect.height(), clip->colorSpace(), i18n("Pasted")); KisPaintLayerSP layer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip->colorSpace()); KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect); image->addNode(layer.data(), image->rootLayer()); doc->setCurrentImage(image); KisPart::instance()->addDocument(doc); KisMainWindow *win = viewManager->mainWindow(); win->addViewAndNotifyLoadingCompleted(doc); } void KisInvertSelectionOperaton::runFromXML(KisViewManager* view, const KisOperationConfiguration& config) { KisSelectionFilter* filter = new KisInvertSelectionFilter(); runFilter(filter, view, config); } void KisSelectionToVectorActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (selection->hasShapeSelection() || !selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); /** * Mark a shape that it belongs to a shape selection */ if(!shape->userData()) { shape->setUserData(new KisShapeSelectionMarker); } KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection")); ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); endAction(ap, KisOperationConfiguration(id()).toXML()); } class KisShapeSelectionPaste : public KoOdfPaste { public: KisShapeSelectionPaste(KisViewManager* view) : m_view(view) { } ~KisShapeSelectionPaste() override { } bool process(const KoXmlElement & body, KoOdfReadStore & odfStore) override { KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store()); KoShapeLoadingContext context(loadingContext, m_view->canvasBase()->shapeController()->resourceManager()); KoXmlElement child; QList shapes; forEachElement(child, body) { KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context); if (shape) { shapes.append(shape); } } if (!shapes.isEmpty()) { KisSelectionToolHelper helper(m_view->canvasBase(), kundo2_i18n("Convert shapes to vector selection")); helper.addSelectionShapes(shapes); } return true; } private: KisViewManager* m_view; }; void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view) { QList shapes = view->canvasBase()->shapeManager()->selection()->selectedShapes(); KoShapeOdfSaveHelper saveHelper(shapes); KoDrag drag; drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QMimeData* mimeData = drag.mimeData(); Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text))); KisShapeSelectionPaste paste(view); paste.paste(KoOdf::Text, mimeData); } void KisSelectionToShapeActionFactory::run(KisViewManager *view) { KisSelectionSP selection = view->selection(); if (!selection->outlineCacheValid()) { return; } QPainterPath selectionOutline = selection->outlineCache(); QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline)); shape->setShapeId(KoPathShapeId); KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); KoShapeStroke* border = new KoShapeStroke(1.0, fgColor.toQColor()); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } int size = params.lineSize; KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } QPainterPath outline = pixelSelection->outlineCache(); QColor color = params.color.toQColor(); KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = params.fillStyle(); KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(params.color); helper.setSelectionOverride(0); QPen pen(Qt::red, size); pen.setJoinStyle(Qt::RoundJoin); if (fillStyle != KisPainter::FillStyleNone) { helper.paintPainterPathQPenFill(outline, pen, params.fillColor); } else { helper.paintPainterPathQPen(outline, pen, params.fillColor); } } else { QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform(); KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline)); shape->setShapeId(KoPathShapeId); KoShapeStroke* border = new KoShapeStroke(size, color); shape->setStroke(border); view->document()->shapeController()->addShape(shape); } image->setModified(); } void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params) { KisImageWSP image = view->image(); if (!image) { return; } KisSelectionSP selection = view->selection(); if (!selection) { return; } KisPixelSelectionSP pixelSelection = selection->projection(); if (!pixelSelection->outlineCacheValid()) { pixelSelection->recalculateOutlineCache(); } KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) { KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager(); QPainterPath outline = pixelSelection->outlineCache(); KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush; KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone; KoColor color = params.color; KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"), image, currentNode, rManager , strokeStyle, fillStyle); helper.setFGColorOverride(color); helper.setSelectionOverride(0); helper.paintPainterPath(outline); image->setModified(); } } diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp index 82ccd85f8f..ca63168097 100644 --- a/libs/ui/canvas/kis_canvas2.cpp +++ b/libs/ui/canvas/kis_canvas2.cpp @@ -1,980 +1,980 @@ /* This file is part of the KDE project * * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) Lukáš Tvrdý , (C) 2010 * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.. */ #include "kis_canvas2.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include "kis_coordinates_converter.h" #include "kis_prescaled_projection.h" #include "kis_image.h" #include "kis_image_barrier_locker.h" #include "KisDocument.h" #include "flake/kis_shape_layer.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_abstract_canvas_widget.h" #include "kis_qpainter_canvas.h" #include "kis_group_layer.h" #include "flake/kis_shape_controller.h" #include "kis_node_manager.h" #include "kis_selection.h" #include "kis_selection_component.h" #include "flake/kis_shape_selection.h" #include "kis_image_config.h" #include "kis_infinity_manager.h" #include "kis_signal_compressor.h" #include "kis_display_color_converter.h" #include "kis_exposure_gamma_correction_interface.h" #include "KisView.h" #include "kis_canvas_controller.h" #include "kis_grid_config.h" #include "kis_animation_player.h" #include "kis_animation_frame_cache.h" #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl.h" #include "kis_fps_decoration.h" #include "KoColorConversionTransformation.h" #include "KisProofingConfiguration.h" #include #include #include "input/kis_input_manager.h" #include "kis_painting_assistants_decoration.h" #include "kis_canvas_updates_compressor.h" #include "KoZoomController.h" class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private { public: KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer view, KoCanvasResourceManager* resourceManager) : coordinatesConverter(coordConverter) , view(view) , shapeManager(parent) , toolProxy(parent) , displayColorConverter(resourceManager, view) { } KisCoordinatesConverter *coordinatesConverter; QPointerview; KisAbstractCanvasWidget *canvasWidget = 0; KoShapeManager shapeManager; bool currentCanvasIsOpenGL; int openGLFilterMode; KisToolProxy toolProxy; KisPrescaledProjectionSP prescaledProjection; bool vastScrolling; KisSignalCompressor updateSignalCompressor; QRect savedUpdateRect; QBitArray channelFlags; KisProofingConfigurationSP proofingConfig; bool softProofing = false; bool gamutCheck = false; bool proofingConfigUpdated = false; KisPopupPalette *popupPalette = 0; KisDisplayColorConverter displayColorConverter; KisCanvasUpdatesCompressor projectionUpdatesCompressor; KisAnimationPlayer *animationPlayer; KisAnimationFrameCacheSP frameCache; bool lodAllowedInCanvas; bool bootstrapLodBlocked; bool effectiveLodAllowedInCanvas() { return lodAllowedInCanvas && !bootstrapLodBlocked; } }; KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc) : KoCanvasBase(sc, resourceManager) , m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager)) { /** * While loading LoD should be blocked. Only when GUI has finished * loading and zoom level settled down, LoD is given a green * light. */ m_d->bootstrapLodBlocked = true; connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished())); m_d->updateSignalCompressor.setDelay(10); m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } void KisCanvas2::setup() { // a bit of duplication from slotConfigChanged() KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled(); createCanvas(cfg.useOpenGL()); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); m_d->animationPlayer = new KisAnimationPlayer(this); connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); /** * We switch the shape manager every time vector layer or * shape selection is activated. Flake does not expect this * and connects all the signals of the global shape manager * to the clients in the constructor. To workaround this we * forward the signals of local shape managers stored in the * vector layers to the signals of global shape manager. So the * sequence of signal deliveries is the following: * * shapeLayer.m_d.canvas.m_shapeManager.selection() -> * shapeLayer -> * shapeController -> * globalShapeManager.selection() */ KisShapeController *kritaShapeController = static_cast(shapeController()->documentBase()); connect(kritaShapeController, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged())); connect(kritaShapeController, SIGNAL(selectionContentChanged()), globalShapeManager(), SIGNAL(selectionContentChanged())); connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)), globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*))); connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate())); } KisCanvas2::~KisCanvas2() { if (m_d->animationPlayer->isPlaying()) { m_d->animationPlayer->forcedStopOnExit(); } delete m_d; } void KisCanvas2::setCanvasWidget(QWidget * widget) { KisAbstractCanvasWidget *tmp = dynamic_cast(widget); Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget"); if (m_d->popupPalette) { m_d->popupPalette->setParent(widget); } if(m_d->canvasWidget != 0) { tmp->setDecorations(m_d->canvasWidget->decorations()); // Redundant check for the constructor case, see below if(viewManager() != 0) viewManager()->inputManager()->removeTrackedCanvas(this); } m_d->canvasWidget = tmp; // Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView // constructor, so the view manager still doesn't exists. if(m_d->canvasWidget != 0 && viewManager() != 0) viewManager()->inputManager()->addTrackedCanvas(this); if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) { KisInfinityManager *manager = new KisInfinityManager(m_d->view, this); manager->setVisible(true); m_d->canvasWidget->addDecoration(manager); } widget->setAutoFillBackground(false); widget->setAttribute(Qt::WA_OpaquePaintEvent); widget->setMouseTracking(true); widget->setAcceptDrops(true); KoCanvasControllerWidget *controller = dynamic_cast(canvasController()); if (controller) { Q_ASSERT(controller->canvas() == this); controller->changeCanvasWidget(widget); } } bool KisCanvas2::canvasIsOpenGL() const { return m_d->currentCanvasIsOpenGL; } KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const { return KisOpenGL::FilterMode(m_d->openGLFilterMode); } void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const { QTransform transform = coordinatesConverter()->imageToDocumentTransform(); const QPoint intSpacing = m_d->view->document()->gridConfig().spacing(); const QPoint intOffset = m_d->view->document()->gridConfig().offset(); QPointF size = transform.map(QPointF(intSpacing)); spacing->rwidth() = size.x(); spacing->rheight() = size.y(); *offset = transform.map(QPointF(intOffset)); } bool KisCanvas2::snapToGrid() const { return m_d->view->document()->gridConfig().snapToGrid(); } qreal KisCanvas2::rotationAngle() const { return m_d->coordinatesConverter->rotationAngle(); } bool KisCanvas2::xAxisMirrored() const { return m_d->coordinatesConverter->xAxisMirrored(); } bool KisCanvas2::yAxisMirrored() const { return m_d->coordinatesConverter->yAxisMirrored(); } void KisCanvas2::channelSelectionChanged() { - KisImageWSP image = this->image(); + KisImageSP image = this->image(); m_d->channelFlags = image->rootLayer()->channelFlags(); + m_d->view->viewManager()->blockUntillOperationsFinishedForced(image); + image->barrierLock(); m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags); startUpdateInPatches(image->bounds()); - image->unlock(); - } void KisCanvas2::addCommand(KUndo2Command *command) { // This method exists to support flake-related operations m_d->view->document()->addCommand(command); } KoShapeManager* KisCanvas2::shapeManager() const { if (!viewManager()) return globalShapeManager(); if (!viewManager()->nodeManager()) return globalShapeManager(); KisLayerSP activeLayer = viewManager()->nodeManager()->activeLayer(); if (activeLayer && activeLayer->isEditable()) { KisShapeLayer * shapeLayer = dynamic_cast(activeLayer.data()); if (shapeLayer) { return shapeLayer->shapeManager(); } KisSelectionSP selection = activeLayer->selection(); if (selection && !selection.isNull()) { if (selection->hasShapeSelection()) { KoShapeManager* m = dynamic_cast(selection->shapeSelection())->shapeManager(); return m; } } } return globalShapeManager(); } KoShapeManager * KisCanvas2::globalShapeManager() const { return &m_d->shapeManager; } void KisCanvas2::updateInputMethodInfo() { // TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget... } const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const { return m_d->coordinatesConverter; } KoViewConverter* KisCanvas2::viewConverter() const { return m_d->coordinatesConverter; } KisInputManager* KisCanvas2::globalInputManager() const { return m_d->view->globalInputManager(); } QWidget* KisCanvas2::canvasWidget() { return m_d->canvasWidget->widget(); } const QWidget* KisCanvas2::canvasWidget() const { return m_d->canvasWidget->widget(); } KoUnit KisCanvas2::unit() const { KoUnit unit(KoUnit::Pixel); KisImageWSP image = m_d->view->image(); if (image) { if (!qFuzzyCompare(image->xRes(), image->yRes())) { warnKrita << "WARNING: resolution of the image is anisotropic" << ppVar(image->xRes()) << ppVar(image->yRes()); } const qreal resolution = image->xRes(); unit.setFactor(resolution); } return unit; } KoToolProxy * KisCanvas2::toolProxy() const { return &m_d->toolProxy; } void KisCanvas2::createQPainterCanvas() { m_d->currentCanvasIsOpenGL = false; KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view); m_d->prescaledProjection = new KisPrescaledProjection(); m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter); m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(), m_d->displayColorConverter.renderingIntent(), m_d->displayColorConverter.conversionFlags()); m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter()); canvasWidget->setPrescaledProjection(m_d->prescaledProjection); setCanvasWidget(canvasWidget); } void KisCanvas2::createOpenGLCanvas() { KisConfig cfg; m_d->openGLFilterMode = cfg.openGLFilteringMode(); m_d->currentCanvasIsOpenGL = true; KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter); m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures()); setCanvasWidget(canvasWidget); if (canvasWidget->needsFpsDebugging() && !decoration(KisFpsDecoration::idTag)) { addDecoration(new KisFpsDecoration(imageView())); } } void KisCanvas2::createCanvas(bool useOpenGL) { // deinitialize previous canvas structures m_d->prescaledProjection = 0; m_d->frameCache = 0; KisConfig cfg; QDesktopWidget dw; const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView())); m_d->displayColorConverter.setMonitorProfile(profile); if (useOpenGL) { if (KisOpenGL::hasOpenGL()) { createOpenGLCanvas(); if (cfg.canvasState() == "OPENGL_FAILED") { // Creating the opengl canvas failed, fall back warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter."; createQPainterCanvas(); } } else { warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n"; createQPainterCanvas(); } } else { createQPainterCanvas(); } if (m_d->popupPalette) { m_d->popupPalette->setParent(m_d->canvasWidget->widget()); } } void KisCanvas2::initializeImage() { KisImageWSP image = m_d->view->image(); m_d->coordinatesConverter->setImage(image); connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection); connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection())); connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig())); connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection); connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32))); connectCurrentCanvas(); } void KisCanvas2::connectCurrentCanvas() { KisImageWSP image = m_d->view->image(); if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->setImage(image); } startResizingImage(); emit imageChanged(image); setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::resetCanvas(bool useOpenGL) { // we cannot reset the canvas before it's created, but this method might be called, // for instance when setting the monitor profile. if (!m_d->canvasWidget) { return; } KisConfig cfg; bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) || (m_d->currentCanvasIsOpenGL && m_d->openGLFilterMode != cfg.openGLFilteringMode()); if (needReset) { createCanvas(useOpenGL); connectCurrentCanvas(); notifyZoomChanged(); } updateCanvasWidgetImpl(); } void KisCanvas2::startUpdateInPatches(const QRect &imageRect) { if (m_d->currentCanvasIsOpenGL) { startUpdateCanvasProjection(imageRect); } else { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < imageRect.height(); y += patchHeight) { for (int x = 0; x < imageRect.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); startUpdateCanvasProjection(patchRect); } } } } void KisCanvas2::setDisplayFilter(QSharedPointer displayFilter) { m_d->displayColorConverter.setDisplayFilter(displayFilter); - KisImageWSP image = this->image(); + KisImageSP image = this->image(); - image->barrierLock(); + m_d->view->viewManager()->blockUntillOperationsFinishedForced(image); + image->barrierLock(); m_d->canvasWidget->setDisplayFilter(displayFilter); - image->unlock(); } QSharedPointer KisCanvas2::displayFilter() const { return m_d->displayColorConverter.displayFilter(); } KisDisplayColorConverter* KisCanvas2::displayColorConverter() const { return &m_d->displayColorConverter; } KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const { QSharedPointer displayFilter = m_d->displayColorConverter.displayFilter(); return displayFilter ? displayFilter->correctionInterface() : KisDumbExposureGammaCorrectionInterface::instance(); } void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { qDebug()<<"Canvas: No proofing config found, generating one."; KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags; if (softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::SoftProofing; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing; } if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) { conversionFlags |= KoColorConversionTransformation::GamutCheck; } else { conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck; } m_d->proofingConfig->conversionFlags = conversionFlags; m_d->proofingConfigUpdated = true; startUpdateInPatches(this->image()->bounds()); } void KisCanvas2::slotSoftProofing(bool softProofing) { m_d->softProofing = softProofing; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotGamutCheck(bool gamutCheck) { m_d->gamutCheck = gamutCheck; setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::slotChangeProofingConfig() { setProofingOptions(m_d->softProofing, m_d->gamutCheck); } void KisCanvas2::setProofingConfigUpdated(bool updated) { m_d->proofingConfigUpdated = updated; } bool KisCanvas2::proofingConfigUpdated() { return m_d->proofingConfigUpdated; } KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const { if (!m_d->proofingConfig) { m_d->proofingConfig = this->image()->proofingConfiguration(); if (!m_d->proofingConfig) { qDebug()<<"Canvas: No proofing config found, generating one."; KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } } return m_d->proofingConfig; } void KisCanvas2::startResizingImage() { KisImageWSP image = this->image(); qint32 w = image->width(); qint32 h = image->height(); emit sigContinueResizeImage(w, h); QRect imageBounds(0, 0, w, h); startUpdateInPatches(imageBounds); } void KisCanvas2::finishResizingImage(qint32 w, qint32 h) { m_d->canvasWidget->finishResizingImage(w, h); } void KisCanvas2::startUpdateCanvasProjection(const QRect & rc) { KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags); if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) { emit sigCanvasCacheUpdated(); } } void KisCanvas2::updateCanvasProjection() { while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) { QRect vRect = m_d->canvasWidget->updateCanvasProjection(info); if (!vRect.isEmpty()) { updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect()); } } // TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas if (m_d->currentCanvasIsOpenGL) { updateCanvasWidgetImpl(); } } void KisCanvas2::slotDoCanvasUpdate() { if (m_d->canvasWidget->isBusy()) { // just restarting the timer updateCanvasWidgetImpl(m_d->savedUpdateRect); return; } if (m_d->savedUpdateRect.isEmpty()) { m_d->canvasWidget->widget()->update(); emit updateCanvasRequested(m_d->canvasWidget->widget()->rect()); } else { emit updateCanvasRequested(m_d->savedUpdateRect); m_d->canvasWidget->widget()->update(m_d->savedUpdateRect); } m_d->savedUpdateRect = QRect(); } void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc) { if (!m_d->updateSignalCompressor.isActive() || !m_d->savedUpdateRect.isEmpty()) { m_d->savedUpdateRect |= rc; } m_d->updateSignalCompressor.start(); } void KisCanvas2::updateCanvas() { updateCanvasWidgetImpl(); } void KisCanvas2::updateCanvas(const QRectF& documentRect) { if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) { updateCanvasWidgetImpl(); } else { // updateCanvas is called from tools, never from the projection // updates, so no need to prescale! QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect(); widgetRect.adjust(-2, -2, 2, 2); if (!widgetRect.isEmpty()) { updateCanvasWidgetImpl(widgetRect); } } } void KisCanvas2::disconnectCanvasObserver(QObject *object) { KoCanvasBase::disconnectCanvasObserver(object); m_d->view->disconnect(object); } void KisCanvas2::notifyZoomChanged() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->notifyZoomChanged(); } notifyLevelOfDetailChange(); updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction } void KisCanvas2::notifyLevelOfDetailChange() { if (!m_d->effectiveLodAllowedInCanvas()) return; KisImageSP image = this->image(); const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom(); KisConfig cfg; const int maxLod = cfg.numMipmapLevels(); int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod); image->setDesiredLevelOfDetail(lod); } void KisCanvas2::preScale() { if (!m_d->currentCanvasIsOpenGL) { Q_ASSERT(m_d->prescaledProjection); m_d->prescaledProjection->preScale(); } } const KoColorProfile * KisCanvas2::monitorProfile() { return m_d->displayColorConverter.monitorProfile(); } KisViewManager* KisCanvas2::viewManager() const { if (m_d->view) { return m_d->view->viewManager(); } return 0; } QPointerKisCanvas2::imageView() const { return m_d->view; } KisImageWSP KisCanvas2::image() const { return m_d->view->image(); } KisImageWSP KisCanvas2::currentImage() const { return m_d->view->image(); } void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset) { QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); m_d->coordinatesConverter->setDocumentOffset(documentOffset); QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft(); QPointF moveOffset = offsetAfter - offsetBefore; if (!m_d->currentCanvasIsOpenGL) m_d->prescaledProjection->viewportMoved(moveOffset); emit documentOffsetUpdateFinished(); updateCanvas(); } void KisCanvas2::slotConfigChanged() { KisConfig cfg; m_d->vastScrolling = cfg.vastScrolling(); resetCanvas(cfg.useOpenGL()); slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget()))); } void KisCanvas2::refetchDataFromImage() { KisImageSP image = this->image(); KisImageBarrierLocker l(image); startUpdateInPatches(image->bounds()); } void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile) { if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return; m_d->displayColorConverter.setMonitorProfile(monitorProfile); { KisImageSP image = this->image(); KisImageBarrierLocker l(image); m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter); } refetchDataFromImage(); } void KisCanvas2::addDecoration(KisCanvasDecorationSP deco) { m_d->canvasWidget->addDecoration(deco); } KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const { return m_d->canvasWidget->decoration(id); } QPoint KisCanvas2::documentOrigin() const { /** * In Krita we don't use document origin anymore. * All the centering when needed (vastScrolling < 0.5) is done * automatically by the KisCoordinatesConverter. */ return QPoint(); } QPoint KisCanvas2::documentOffset() const { return m_d->coordinatesConverter->documentOffset(); } void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager) { m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(), m_d->view->resourceProvider(), m_d->canvasWidget->widget()); connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotZoomChanged(int))); connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas())); m_d->popupPalette->showPopupPalette(false); } void KisCanvas2::slotZoomChanged(int zoom ) { m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom notifyZoomChanged(); } void KisCanvas2::setCursor(const QCursor &cursor) { canvasWidget()->setCursor(cursor); } KisAnimationFrameCacheSP KisCanvas2::frameCache() const { return m_d->frameCache; } KisAnimationPlayer *KisCanvas2::animationPlayer() const { return m_d->animationPlayer; } void KisCanvas2::slotSelectionChanged() { KisShapeLayer* shapeLayer = dynamic_cast(viewManager()->activeLayer().data()); if (!shapeLayer) { return; } m_d->shapeManager.selection()->deselectAll(); Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) { m_d->shapeManager.selection()->select(shape); } } bool KisCanvas2::isPopupPaletteVisible() const { if (!m_d->popupPalette) { return false; } return m_d->popupPalette->isVisible(); } void KisCanvas2::setWrapAroundViewingMode(bool value) { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { infinityDecoration->setVisible(!value); } m_d->canvasWidget->setWrapAroundViewingMode(value); } bool KisCanvas2::wrapAroundViewingMode() const { KisCanvasDecorationSP infinityDecoration = m_d->canvasWidget->decoration(INFINITY_DECORATION_ID); if (infinityDecoration) { return !(infinityDecoration->visible()); } return false; } void KisCanvas2::bootstrapFinished() { if (!m_d->bootstrapLodBlocked) return; m_d->bootstrapLodBlocked = false; setLodAllowedInCanvas(m_d->lodAllowedInCanvas); } void KisCanvas2::setLodAllowedInCanvas(bool value) { if (!KisOpenGL::supportsLoD()) { qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support"; } m_d->lodAllowedInCanvas = value && m_d->currentCanvasIsOpenGL && KisOpenGL::supportsLoD() && (m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode || m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering); KisImageSP image = this->image(); if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) { image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas()); notifyLevelOfDetailChange(); } KisConfig cfg; cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas); } bool KisCanvas2::lodAllowedInCanvas() const { return m_d->lodAllowedInCanvas; } void KisCanvas2::slotShowPopupPalette(const QPoint &p) { if (!m_d->popupPalette) { return; } m_d->popupPalette->showPopupPalette(p); } KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const { KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration"); return qobject_cast(deco.data()); } diff --git a/libs/ui/dialogs/kis_delayed_save_dialog.cpp b/libs/ui/dialogs/kis_delayed_save_dialog.cpp index 757cede77e..6b8afdca27 100644 --- a/libs/ui/dialogs/kis_delayed_save_dialog.cpp +++ b/libs/ui/dialogs/kis_delayed_save_dialog.cpp @@ -1,85 +1,122 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_delayed_save_dialog.h" #include "ui_kis_delayed_save_dialog.h" #include +#include +#include #include "kis_debug.h" #include "kis_image.h" #include "kis_composite_progress_proxy.h" struct KisDelayedSaveDialog::Private { - Private(KisImageSP _image) : image(_image) {} + Private(KisImageSP _image, int _busyWait, Type _type) : image(_image), busyWait(_busyWait), type(_type) {} KisImageSP image; QTimer updateTimer; + int busyWait; + + + bool checkImageIdle() { + const bool allowLocked = type != SaveDialog; + return image->isIdle(allowLocked); + } + +private: + Type type; }; -KisDelayedSaveDialog::KisDelayedSaveDialog(KisImageSP image, QWidget *parent) +KisDelayedSaveDialog::KisDelayedSaveDialog(KisImageSP image, Type type, int busyWait, QWidget *parent) : QDialog(parent), ui(new Ui::KisDelayedSaveDialog), - m_d(new Private(image)) + m_d(new Private(image, busyWait, type)) { KIS_ASSERT_RECOVER_NOOP(image); ui->setupUi(this); - connect(ui->bnDontWait, SIGNAL(clicked()), SLOT(accept())); + if (type == SaveDialog) { + connect(ui->bnDontWait, SIGNAL(clicked()), SLOT(accept())); + connect(ui->bnCancel, SIGNAL(clicked()), SLOT(slotCancelRequested())); + } else { + ui->bnDontSave->setText(i18n("Cancel")); + ui->bnDontWait->setVisible(false); + ui->bnCancel->setVisible(false); + + if (type == ForcedDialog) { + ui->bnDontSave->setVisible(false); + } + } + connect(ui->bnDontSave, SIGNAL(clicked()), SLOT(reject())); - connect(ui->bnCancel, SIGNAL(clicked()), SLOT(slotCancelRequested())); connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(slotTimerTimeout())); m_d->image->compositeProgressProxy()->addProxy(ui->progressBar); } KisDelayedSaveDialog::~KisDelayedSaveDialog() { m_d->image->compositeProgressProxy()->removeProxy(ui->progressBar); delete ui; } void KisDelayedSaveDialog::blockIfImageIsBusy() { - if (m_d->image->isIdle()) { + if (m_d->checkImageIdle()) { setResult(Accepted); return; } m_d->image->requestStrokeEnd(); + QElapsedTimer t; + t.start(); + + while (t.elapsed() < m_d->busyWait) { + QApplication::processEvents(); + + if (m_d->checkImageIdle()) { + setResult(Accepted); + return; + } + + QThread::yieldCurrentThread(); + } + m_d->updateTimer.start(200); exec(); m_d->updateTimer.stop(); } void KisDelayedSaveDialog::slotTimerTimeout() { - if (m_d->image->isIdle()) { + if (m_d->checkImageIdle()) { accept(); } } void KisDelayedSaveDialog::slotCancelRequested() { m_d->image->requestStrokeCancellation(); } diff --git a/libs/ui/dialogs/kis_delayed_save_dialog.h b/libs/ui/dialogs/kis_delayed_save_dialog.h index 40e742cd00..68d6bc887b 100644 --- a/libs/ui/dialogs/kis_delayed_save_dialog.h +++ b/libs/ui/dialogs/kis_delayed_save_dialog.h @@ -1,52 +1,59 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DELAYED_SAVE_DIALOG_H #define KIS_DELAYED_SAVE_DIALOG_H #include #include #include "kis_types.h" namespace Ui { class KisDelayedSaveDialog; } class KisDelayedSaveDialog : public QDialog { Q_OBJECT public: - explicit KisDelayedSaveDialog(KisImageSP image, QWidget *parent = 0); + enum Type { + SaveDialog, + GeneralDialog, + ForcedDialog + }; + +public: + explicit KisDelayedSaveDialog(KisImageSP image, Type type, int busyWait, QWidget *parent = 0); ~KisDelayedSaveDialog(); void blockIfImageIsBusy(); private Q_SLOTS: void slotTimerTimeout(); void slotCancelRequested(); private: Ui::KisDelayedSaveDialog *ui; private: struct Private; const QScopedPointer m_d; }; #endif // KIS_DELAYED_SAVE_DIALOG_H diff --git a/libs/ui/dialogs/kis_dlg_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc index 452c1f4b69..ad11b2b206 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.cc +++ b/libs/ui/dialogs/kis_dlg_preferences.cc @@ -1,1052 +1,1084 @@ /* * preferencesdlg.cc - part of KImageShop * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_preferences.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 "KoID.h" #include #include #include #include #include #include "kis_action_registry.h" #include "widgets/squeezedcombobox.h" #include "kis_clipboard.h" #include "widgets/kis_cmb_idlist.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" #include "KoColorConversionTransformation.h" #include "kis_cursor.h" #include "kis_config.h" #include "kis_canvas_resource_provider.h" #include "kis_preference_set_registry.h" #include "kis_color_manager.h" #include "KisProofingConfiguration.h" #include "kis_image_config.h" #include "slider_and_spin_box_sync.h" // for the performance update #include #include "input/config/kis_input_configuration_page.h" GeneralTab::GeneralTab(QWidget *_parent, const char *_name) : WdgGeneralSettings(_parent, _name) { KisConfig cfg; m_cmbCursorShape->addItem(i18n("No Cursor")); m_cmbCursorShape->addItem(i18n("Tool Icon")); m_cmbCursorShape->addItem(i18n("Arrow")); m_cmbCursorShape->addItem(i18n("Small Circle")); m_cmbCursorShape->addItem(i18n("Crosshair")); m_cmbCursorShape->addItem(i18n("Triangle Righthanded")); m_cmbCursorShape->addItem(i18n("Triangle Lefthanded")); m_cmbCursorShape->addItem(i18n("Black Pixel")); m_cmbCursorShape->addItem(i18n("White Pixel")); m_cmbOutlineShape->addItem(i18n("No Outline")); m_cmbOutlineShape->addItem(i18n("Circle Outline")); m_cmbOutlineShape->addItem(i18n("Preview Outline")); m_cmbOutlineShape->addItem(i18n("Tilt Outline")); m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle()); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle()); chkShowRootLayer->setChecked(cfg.showRootLayer()); int autosaveInterval = cfg.autoSaveInterval(); //convert to minutes m_autosaveSpinBox->setValue(autosaveInterval / 60); m_autosaveCheckBox->setChecked(autosaveInterval > 0); m_undoStackSize->setValue(cfg.undoStackLimit()); m_backupFileCheckBox->setChecked(cfg.backupFile()); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting()); m_hideSplashScreen->setChecked(cfg.hideSplashScreen()); m_cmbMDIType->setCurrentIndex(cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView)); m_chkRubberBand->setChecked(cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets()); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor()); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage()); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages()); m_chkCompressKra->setChecked(cfg.compressKra()); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker()); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt()); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport()); connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage())); connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage())); } void GeneralTab::setDefault() { KisConfig cfg; m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true)); m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true)); chkShowRootLayer->setChecked(cfg.showRootLayer(true)); m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0); //convert to minutes m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60); m_undoStackSize->setValue(cfg.undoStackLimit(true)); m_backupFileCheckBox->setChecked(cfg.backupFile(true)); m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true)); m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true)); m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView); m_chkRubberBand->setChecked(cfg.useOpenGL(true)); m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true)); KoColor mdiColor; mdiColor.fromQColor(cfg.getMDIBackgroundColor(true)); m_mdiColor->setColor(mdiColor); m_backgroundimage->setText(cfg.getMDIBackgroundImage(true)); m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true)); m_chkCompressKra->setChecked(cfg.compressKra(true)); m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true)); m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true)); m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true)); } CursorStyle GeneralTab::cursorStyle() { return (CursorStyle)m_cmbCursorShape->currentIndex(); } OutlineStyle GeneralTab::outlineStyle() { return (OutlineStyle)m_cmbOutlineShape->currentIndex(); } bool GeneralTab::showRootLayer() { return chkShowRootLayer->isChecked(); } int GeneralTab::autoSaveInterval() { //convert to seconds return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value()*60 : 0; } int GeneralTab::undoStackSize() { return m_undoStackSize->value(); } bool GeneralTab::showOutlineWhilePainting() { return m_showOutlinePainting->isChecked(); } bool GeneralTab::hideSplashScreen() { return m_hideSplashScreen->isChecked(); } int GeneralTab::mdiMode() { return m_cmbMDIType->currentIndex(); } int GeneralTab::favoritePresets() { return m_favoritePresetsSpinBox->value(); } bool GeneralTab::showCanvasMessages() { return m_chkCanvasMessages->isChecked(); } bool GeneralTab::compressKra() { return m_chkCompressKra->isChecked(); } bool GeneralTab::toolOptionsInDocker() { return m_radioToolOptionsInDocker->isChecked(); } bool GeneralTab::switchSelectionCtrlAlt() { return m_chkSwitchSelectionCtrlAlt->isChecked(); } bool GeneralTab::convertToImageColorspaceOnImport() { return m_chkConvertOnImport->isChecked(); } void GeneralTab::getBackgroundImage() { KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages"); dialog.setCaption(i18n("Select a Background Image")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setImageFilters(); QString fn = dialog.filename(); // dialog box was canceled or somehow no file was selected if (fn.isEmpty()) { return; } QImage image(fn); if (image.isNull()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn)); } else { m_backgroundimage->setText(fn); } } void GeneralTab::clearBackgroundImage() { // clearing the background image text will implicitly make the background color be used m_backgroundimage->setText(""); } +#include "kactioncollection.h" +#include "KisActionsSnapshot.h" + ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgShortcutSettings(this); l->addWidget(m_page, 0, 0); - KisPart::instance()->loadActions(); - KisActionRegistry::instance()->setupDialog(m_page); + + m_snapshot.reset(new KisActionsSnapshot); + + KActionCollection *collection = + KisPart::instance()->currentMainwindow()->actionCollection(); + + Q_FOREACH (QAction *action, collection->actions()) { + m_snapshot->addAction(action->objectName(), action); + } + + QMap sortedCollections = + m_snapshot->actionCollections(); + + for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) { + m_page->addCollection(it.value(), it.key()); + } +} + +ShortcutSettingsTab::~ShortcutSettingsTab() +{ } void ShortcutSettingsTab::setDefault() { m_page->allDefault(); } void ShortcutSettingsTab::saveChanges() { m_page->save(); KisActionRegistry::instance()->settingsPageSaved(); } -void ShortcutSettingsTab::revertChanges() +void ShortcutSettingsTab::cancelChanges() { - m_page->allDefault(); + m_page->undo(); } ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); // XXX: Make sure only profiles that fit the specified color model // are shown in the profile combos QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgColorSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile()); connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool))); - if (KisColorManager::instance()->devices().size() > 0) { - m_page->chkUseSystemMonitorProfile->setVisible(false); - } m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys()); m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace()); m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open")); m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") ); connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile())); - QGridLayout *monitorProfileGrid = new QGridLayout(m_page->monitorprofileholder); + QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1)); - monitorProfileGrid->addWidget(lbl, i, 0); m_monitorProfileLabels << lbl; SqueezedComboBox *cmb = new SqueezedComboBox(); - monitorProfileGrid->addWidget(cmb, i, 1); + cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + monitorProfileGrid->addRow(lbl, cmb); m_monitorProfileWidgets << cmb; } refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation()); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization()); KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); m_page->sldAdaptationState->setMaximum(20); m_page->sldAdaptationState->setMinimum(0); m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20); - const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); + //probably this should become the screenprofile? + KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); + ga.fromKoColor(proofingConfig->warningColor); + m_page->gamutAlarm->setColor(ga); + + const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel, + proofingConfig->proofingDepth, + proofingConfig->proofingProfile); m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB); m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR); m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour()); Q_ASSERT(button); if (button) { button->setChecked(true); } m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent()); toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile()); } void ColorSettingsTab::installProfile() { KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC"); dialog.setCaption(i18n("Install Color Profiles")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile"); QStringList profileNames = dialog.filenames(); KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); Q_ASSERT(iccEngine); QString saveLocation = KoResourcePaths::saveLocation("icc_profiles"); Q_FOREACH (const QString &profileName, profileNames) { - QUrl file(profileName); - if (!QFile::copy(profileName, saveLocation + file.fileName())) { - dbgKrita << "Could not install profile!"; - return; + if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) { + qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName(); + continue; } - iccEngine->addProfile(saveLocation + file.fileName()); - + iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName()); } KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile) { if (useSystemProfile) { KisConfig cfg; QStringList devices = KisColorManager::instance()->devices(); if (devices.size() == QApplication::desktop()->screenCount()) { for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); QString monitorForScreen = cfg.monitorForScreen(i, devices[i]); Q_FOREACH (const QString &device, devices) { m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device); if (devices[i] == monitorForScreen) { m_monitorProfileWidgets[i]->setCurrentIndex(i); } } } } } else { KisConfig cfg; refillMonitorProfiles(KoID("RGBA", "")); for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) { m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i)); } } } } void ColorSettingsTab::setDefault() { m_page->cmbWorkingColorSpace->setCurrent("RGBA"); refillMonitorProfiles(KoID("RGBA", "")); KisConfig cfg; KisImageConfig cfgImage; KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration(); const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile); m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace); m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent); m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation)); m_page->sldAdaptationState->setValue(0); //probably this should become the screenprofile? KoColor ga(KoColorSpaceRegistry::instance()->rgb8()); ga.fromKoColor(proofingConfig->warningColor); m_page->gamutAlarm->setColor(ga); m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true)); m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true)); m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true)); m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true)); QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true)); Q_ASSERT(button); if (button) { button->setChecked(true); } } void ColorSettingsTab::refillMonitorProfiles(const KoID & s) { const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s.id()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->clear(); } if (!csf) return; - QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); + QMap profileList; + Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(csf)) { + profileList[profile->name()] = profile; + } - Q_FOREACH (const KoColorProfile *profile, profileList) { -// //dbgKrita << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); + Q_FOREACH (const KoColorProfile *profile, profileList.values()) { + //qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile(); if (profile->isSuitableForDisplay()) { for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileWidgets[i]->addSqueezedItem(profile->name()); } } } for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1)); m_monitorProfileWidgets[i]->setCurrent(csf->defaultProfile()); } } //--------------------------------------------------------------------------------------------------- void TabletSettingsTab::setDefault() { KisCubicCurve curve; curve.fromString(DEFAULT_CURVE_STRING); m_page->pressureCurve->setCurve(curve); } TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent) { setObjectName(name); QGridLayout * l = new QGridLayout(this); l->setMargin(0); m_page = new WdgTabletSettings(this); l->addWidget(m_page, 0, 0); KisConfig cfg; KisCubicCurve curve; curve.fromString( cfg.pressureTabletCurve() ); m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_page->pressureCurve->setCurve(curve); } //--------------------------------------------------------------------------------------------------- #include "kis_acyclic_signal_connector.h" int getTotalRAM() { KisImageConfig cfg; return cfg.totalRAM(); } int PerformanceTab::realTilesRAM() { return intMemoryLimit->value() - intPoolLimit->value(); } PerformanceTab::PerformanceTab(QWidget *parent, const char *name) : WdgPerformanceSettings(parent, name) { KisImageConfig cfg; const int totalRAM = cfg.totalRAM(); lblTotalMemory->setText(i18n("%1 MiB", totalRAM)); sliderMemoryLimit->setSuffix(i18n(" %")); sliderMemoryLimit->setRange(1, 100, 2); sliderMemoryLimit->setSingleStep(0.01); sliderPoolLimit->setSuffix(i18n(" %")); sliderPoolLimit->setRange(0, 20, 2); sliderMemoryLimit->setSingleStep(0.01); sliderUndoLimit->setSuffix(i18n(" %")); sliderUndoLimit->setRange(0, 50, 2); sliderMemoryLimit->setSingleStep(0.01); intMemoryLimit->setMinimumWidth(80); intPoolLimit->setMinimumWidth(80); intUndoLimit->setMinimumWidth(80); SliderAndSpinBoxSync *sync1 = new SliderAndSpinBoxSync(sliderMemoryLimit, intMemoryLimit, getTotalRAM); sync1->slotParentValueChanged(); m_syncs << sync1; SliderAndSpinBoxSync *sync2 = new SliderAndSpinBoxSync(sliderPoolLimit, intPoolLimit, std::bind(&KisIntParseSpinBox::value, intMemoryLimit)); connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged())); sync2->slotParentValueChanged(); m_syncs << sync2; SliderAndSpinBoxSync *sync3 = new SliderAndSpinBoxSync(sliderUndoLimit, intUndoLimit, std::bind(&PerformanceTab::realTilesRAM, this)); connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged())); sync3->slotParentValueChanged(); m_syncs << sync3; sliderSwapSize->setSuffix(i18n(" GiB")); sliderSwapSize->setRange(1, 64); intSwapSize->setRange(1, 64); KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this); swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)), intSwapSize, SLOT(setValue(int))); swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)), sliderSwapSize, SLOT(setValue(int))); lblSwapFileLocation->setText(cfg.swapDir()); connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir())); load(false); } PerformanceTab::~PerformanceTab() { qDeleteAll(m_syncs); } void PerformanceTab::load(bool requestDefault) { KisImageConfig cfg; sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault)); sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault)); sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault)); chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault)); chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault)); sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024); lblSwapFileLocation->setText(cfg.swapDir(requestDefault)); { KisConfig cfg2; chkOpenGLLogging->setChecked(cfg2.enableOpenGLDebugging(requestDefault)); chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault)); } } void PerformanceTab::save() { KisImageConfig cfg; cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value()); cfg.setMemorySoftLimitPercent(sliderUndoLimit->value()); cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value()); cfg.setEnablePerfLog(chkPerformanceLogging->isChecked()); cfg.setEnableProgressReporting(chkProgressReporting->isChecked()); cfg.setMaxSwapSize(sliderSwapSize->value() * 1024); cfg.setSwapDir(lblSwapFileLocation->text()); { KisConfig cfg2; cfg2.setEnableOpenGLDebugging(chkOpenGLLogging->isChecked()); cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked()); } } void PerformanceTab::selectSwapDir() { KisImageConfig cfg; QString swapDir = cfg.swapDir(); swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir); lblSwapFileLocation->setText(swapDir); } //--------------------------------------------------------------------------------------------------- #include "KoColor.h" DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name) : WdgDisplaySettings(parent, name) { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL()); chkUseTextureBuffer->setEnabled(cfg.useOpenGL()); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer()); chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings()); chkDisableVsync->setEnabled(cfg.useOpenGL()); chkDisableVsync->setChecked(cfg.disableVSync()); cmbFilterMode->setEnabled(cfg.useOpenGL()); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode()); // Don't show the high quality filtering mode if it's not available - if (!KisOpenGL::hasOpenGL3()) { + if (!KisOpenGL::supportsLoD()) { cmbFilterMode->removeItem(3); } } if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") { grpOpenGL->setVisible(false); grpOpenGL->setMaximumHeight(0); } KoColor c; c.fromQColor(cfg.selectionOverlayMaskColor()); c.setOpacity(1.0); btnSelectionOverlayColor->setColor(c); sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2); sldSelectionOverlayOpacity->setSingleStep(0.05); sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF()); intCheckSize->setValue(cfg.checkSize()); chkMoving->setChecked(cfg.scrollCheckers()); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1()); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2()); colorChecks2->setColor(ck2); KoColor cb(KoColorSpaceRegistry::instance()->rgb8()); cb.fromQColor(cfg.canvasBorderColor()); canvasBorder->setColor(cb); hideScrollbars->setChecked(cfg.hideScrollbars()); chkCurveAntialiasing->setChecked(cfg.antialiasCurves()); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline()); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor()); chkHidePopups->setChecked(cfg.hidePopups()); connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool))); } void DisplaySettingsTab::setDefault() { KisConfig cfg; if (!KisOpenGL::hasOpenGL()) { grpOpenGL->setEnabled(false); grpOpenGL->setChecked(false); chkUseTextureBuffer->setEnabled(false); chkDisableVsync->setEnabled(false); cmbFilterMode->setEnabled(false); } else { grpOpenGL->setEnabled(true); grpOpenGL->setChecked(cfg.useOpenGL(true)); chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true)); chkUseTextureBuffer->setEnabled(true); chkDisableVsync->setEnabled(true); chkDisableVsync->setChecked(cfg.disableVSync(true)); cmbFilterMode->setEnabled(true); cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true)); } chkMoving->setChecked(cfg.scrollCheckers(true)); intCheckSize->setValue(cfg.checkSize(true)); KoColor ck1(KoColorSpaceRegistry::instance()->rgb8()); ck1.fromQColor(cfg.checkersColor1(true)); colorChecks1->setColor(ck1); KoColor ck2(KoColorSpaceRegistry::instance()->rgb8()); ck2.fromQColor(cfg.checkersColor2(true)); colorChecks2->setColor(ck2); KoColor cvb(KoColorSpaceRegistry::instance()->rgb8()); cvb.fromQColor(cfg.canvasBorderColor(true)); canvasBorder->setColor(cvb); hideScrollbars->setChecked(cfg.hideScrollbars(true)); chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true)); chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true)); chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true)); chkHidePopups->setChecked(cfg.hidePopups(true)); } void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked) { chkUseTextureBuffer->setEnabled(isChecked); chkDisableVsync->setEnabled(isChecked); cmbFilterMode->setEnabled(isChecked); } //--------------------------------------------------------------------------------------------------- FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent) { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen()); chkMenu->setChecked(cfg.hideMenuFullscreen()); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen()); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen()); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen()); chkToolbar->setChecked(cfg.hideToolbarFullscreen()); } void FullscreenSettingsTab::setDefault() { KisConfig cfg; chkDockers->setChecked(cfg.hideDockersFullscreen(true)); chkMenu->setChecked(cfg.hideMenuFullscreen(true)); chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true)); chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true)); chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true)); chkToolbar->setChecked(cfg.hideToolbarFullscreen(true)); } //--------------------------------------------------------------------------------------------------- KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name) : KPageDialog(parent) { Q_UNUSED(name); - setWindowTitle(i18n("Preferences")); + setWindowTitle(i18n("Configure Krita")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(KPageDialog::List); // General KoVBox *vbox = new KoVBox(); KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General")); page->setObjectName("general"); page->setHeader(i18n("General")); page->setIcon(KisIconUtils::loadIcon("go-home")); addPage(page); m_general = new GeneralTab(vbox); // Shortcuts vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts")); page->setObjectName("shortcuts"); page->setHeader(i18n("Shortcuts")); page->setIcon(KisIconUtils::loadIcon("document-export")); addPage(page); m_shortcutSettings = new ShortcutSettingsTab(vbox); connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges())); + connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges())); // Canvas input settings m_inputConfiguration = new KisInputConfigurationPage(); page = addPage(m_inputConfiguration, i18n("Canvas Input Settings")); page->setHeader(i18n("Canvas Input")); page->setObjectName("canvasinput"); page->setIcon(KisIconUtils::loadIcon("configure")); // Display vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Display")); page->setObjectName("display"); page->setHeader(i18n("Display")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display")); addPage(page); m_displaySettings = new DisplaySettingsTab(vbox); // Color vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Color Management")); page->setObjectName("colormanagement"); page->setHeader(i18n("Color")); page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color")); addPage(page); m_colorSettings = new ColorSettingsTab(vbox); // Performance vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Performance")); page->setObjectName("performance"); page->setHeader(i18n("Performance")); page->setIcon(KisIconUtils::loadIcon("applications-system")); addPage(page); m_performanceSettings = new PerformanceTab(vbox); // Tablet vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Tablet settings")); page->setObjectName("tablet"); page->setHeader(i18n("Tablet")); page->setIcon(KisIconUtils::loadIcon("document-edit")); addPage(page); m_tabletSettings = new TabletSettingsTab(vbox); // full-screen mode vbox = new KoVBox(); page = new KPageWidgetItem(vbox, i18n("Canvas-only settings")); page->setObjectName("canvasonly"); page->setHeader(i18n("Canvas-only")); page->setIcon(KisIconUtils::loadIcon("folder-pictures")); addPage(page); m_fullscreenSettings = new FullscreenSettingsTab(vbox); // Author profiles m_authorPage = new KoConfigAuthorPage(); page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" )); page->setObjectName("author"); page->setHeader(i18n("Author")); page->setIcon(KisIconUtils::loadIcon("im-user")); QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults); connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges())); connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges())); KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) { KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet(); vbox = new KoVBox(); page = new KPageWidgetItem(vbox, preferenceSet->name()); page->setHeader(preferenceSet->header()); page->setIcon(preferenceSet->icon()); addPage(page); preferenceSet->setParent(vbox); preferenceSet->loadPreferences(); connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection); connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection); } connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault())); } KisDlgPreferences::~KisDlgPreferences() { } void KisDlgPreferences::slotDefault() { if (currentPage()->objectName() == "general") { m_general->setDefault(); } else if (currentPage()->objectName() == "shortcuts") { m_shortcutSettings->setDefault(); } else if (currentPage()->objectName() == "display") { m_displaySettings->setDefault(); } else if (currentPage()->objectName() == "colormanagement") { m_colorSettings->setDefault(); } else if (currentPage()->objectName() == "performance") { m_performanceSettings->load(true); } else if (currentPage()->objectName() == "tablet") { m_tabletSettings->setDefault(); } else if (currentPage()->objectName() == "canvasonly") { m_fullscreenSettings->setDefault(); } else if (currentPage()->objectName() == "canvasinput") { m_inputConfiguration->setDefaults(); } } bool KisDlgPreferences::editPreferences() { KisDlgPreferences* dialog; dialog = new KisDlgPreferences(); bool baccept = (dialog->exec() == Accepted); if (baccept) { // General settings KisConfig cfg; cfg.setNewCursorStyle(dialog->m_general->cursorStyle()); cfg.setNewOutlineStyle(dialog->m_general->outlineStyle()); cfg.setShowRootLayer(dialog->m_general->showRootLayer()); cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting()); cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen()); cfg.writeEntry("mdi_viewmode", dialog->m_general->mdiMode()); cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor()); cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text()); cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval()); cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages()); cfg.setCompressKra(dialog->m_general->compressKra()); cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker()); cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt()); cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport()); KisPart *part = KisPart::instance(); if (part) { Q_FOREACH (QPointer doc, part->documents()) { if (doc) { doc->setAutoSaveDelay(dialog->m_general->autoSaveInterval()); doc->setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked()); doc->undoStack()->setUndoLimit(dialog->m_general->undoStackSize()); } } } cfg.setUndoStackLimit(dialog->m_general->undoStackSize()); cfg.setFavoritePresets(dialog->m_general->favoritePresets()); // Color settings cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) { if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) { int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex(); QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString(); cfg.setMonitorForScreen(i, monitorid); } else { cfg.setMonitorProfile(i, dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(), dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()); } } cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id()); KisImageConfig cfgImage; - cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), dialog->m_colorSettings->m_page->gamutAlarm->color(), (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); + cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(), + dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(), + dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(), + dialog->m_colorSettings->m_page->gamutAlarm->color(), + (double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20); cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked()); cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked()); cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId()); cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex()); // Tablet settings cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() ); dialog->m_performanceSettings->save(); if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked()) cfg.setCanvasState("TRY_OPENGL"); cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked()); cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked()); cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex()); cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked()); cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value()); cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked()); cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor()); cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor()); cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor()); cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked()); KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color(); c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value()); cfg.setSelectionOverlayMaskColor(c.toQColor()); cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked()); cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked()); cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked()); cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked()); cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState()); cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState()); cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState()); cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState()); cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState()); cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState()); dialog->m_authorPage->apply(); } delete dialog; return baccept; } diff --git a/libs/ui/dialogs/kis_dlg_preferences.h b/libs/ui/dialogs/kis_dlg_preferences.h index 1fdcccbc4c..db24fc2f3d 100644 --- a/libs/ui/dialogs/kis_dlg_preferences.h +++ b/libs/ui/dialogs/kis_dlg_preferences.h @@ -1,326 +1,330 @@ /* * preferencesdlg.h - part of KImageShop^WKrita * * Copyright (c) 1999 Michael Koch * Copyright (c) 2003-2011 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_DLG_PREFERENCES_H_ #define _KIS_DLG_PREFERENCES_H_ #include #include #include "kis_global.h" #include "widgets/squeezedcombobox.h" #include "ui_wdggeneralsettings.h" #include "ui_wdgdisplaysettings.h" #include "ui_wdgcolorsettings.h" #include "ui_wdgtabletsettings.h" #include "ui_wdgperformancesettings.h" #include "ui_wdgfullscreensettings.h" #include "KisShortcutsDialog.h" class KoID; class KisInputConfigurationPage; class KoConfigAuthorPage; /** * "General"-tab for preferences dialog */ class WdgGeneralSettings : public QWidget, public Ui::WdgGeneralSettings { Q_OBJECT public: WdgGeneralSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); chkShowRootLayer->setVisible(false); } }; class GeneralTab : public WdgGeneralSettings { Q_OBJECT public: GeneralTab(QWidget *parent = 0, const char *name = 0); CursorStyle cursorStyle(); OutlineStyle outlineStyle(); bool showRootLayer(); int autoSaveInterval(); void setDefault(); int undoStackSize(); bool showOutlineWhilePainting(); bool hideSplashScreen(); int mdiMode(); int favoritePresets(); bool showCanvasMessages(); bool compressKra(); bool toolOptionsInDocker(); bool switchSelectionCtrlAlt(); bool convertToImageColorspaceOnImport(); private Q_SLOTS: void getBackgroundImage(); void clearBackgroundImage(); }; /** * "Shortcuts" tab for preferences dialog */ class WdgShortcutSettings : public KisShortcutsDialog { Q_OBJECT public: WdgShortcutSettings(QWidget *parent) : KisShortcutsDialog(KisShortcutsEditor::AllActions, KisShortcutsEditor::LetterShortcutsAllowed, parent) { } }; +class KisActionsSnapshot; + class ShortcutSettingsTab : public QWidget { Q_OBJECT public: ShortcutSettingsTab(QWidget *parent = 0, const char *name = 0); + ~ShortcutSettingsTab(); public: void setDefault(); WdgShortcutSettings *m_page; + QScopedPointer m_snapshot; public Q_SLOTS: void saveChanges(); - void revertChanges(); + void cancelChanges(); }; /** * "Color" tab for preferences dialog */ class WdgColorSettings : public QWidget, public Ui::WdgColorSettings { Q_OBJECT public: WdgColorSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class ColorSettingsTab : public QWidget { Q_OBJECT public: ColorSettingsTab(QWidget *parent = 0, const char *name = 0); private Q_SLOTS: void refillMonitorProfiles(const KoID & s); void installProfile(); void toggleAllowMonitorProfileSelection(bool useSystemProfile); public: void setDefault(); WdgColorSettings *m_page; QButtonGroup m_pasteBehaviourGroup; QList m_monitorProfileLabels; QList m_monitorProfileWidgets; }; //======================= class WdgTabletSettings : public QWidget, public Ui::WdgTabletSettings { Q_OBJECT public: WdgTabletSettings(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class TabletSettingsTab : public QWidget { Q_OBJECT public: TabletSettingsTab(QWidget *parent = 0, const char *name = 0); public: void setDefault(); WdgTabletSettings *m_page; }; //======================= /** * "Performance"-tab for preferences dialog */ class SliderAndSpinBoxSync; class WdgPerformanceSettings : public QWidget, public Ui::WdgPerformanceSettings { Q_OBJECT public: WdgPerformanceSettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; class PerformanceTab : public WdgPerformanceSettings { Q_OBJECT public: PerformanceTab(QWidget *parent = 0, const char *name = 0); ~PerformanceTab(); void load(bool requestDefault); void save(); private Q_SLOTS: void selectSwapDir(); private: int realTilesRAM(); private: QVector m_syncs; }; //======================= class WdgDisplaySettings : public QWidget, public Ui::WdgDisplaySettings { Q_OBJECT public: WdgDisplaySettings(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); setupUi(this); } }; /** * Display settings tab for preferences dialog */ class DisplaySettingsTab : public WdgDisplaySettings { Q_OBJECT public: DisplaySettingsTab(QWidget *parent = 0, const char *name = 0); public: void setDefault(); protected Q_SLOTS: void slotUseOpenGLToggled(bool isChecked); public: }; //======================= /** * Full screen settings tab for preferences dialog */ class WdgFullscreenSettingsBase : public QWidget, public Ui::WdgFullscreenSettings { Q_OBJECT public: WdgFullscreenSettingsBase(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class FullscreenSettingsTab : public WdgFullscreenSettingsBase { Q_OBJECT public: FullscreenSettingsTab(QWidget *parent); public: void setDefault(); }; //======================= /** * Preferences dialog of KImageShop^WKrayon^WKrita */ class KisDlgPreferences : public KPageDialog { Q_OBJECT public: static bool editPreferences(); protected: KisDlgPreferences(QWidget *parent = 0, const char *name = 0); ~KisDlgPreferences(); protected: GeneralTab *m_general; ShortcutSettingsTab *m_shortcutSettings; ColorSettingsTab *m_colorSettings; PerformanceTab *m_performanceSettings; DisplaySettingsTab *m_displaySettings; TabletSettingsTab *m_tabletSettings; FullscreenSettingsTab *m_fullscreenSettings; KisInputConfigurationPage *m_inputConfiguration; KoConfigAuthorPage *m_authorPage; protected Q_SLOTS: void slotDefault(); }; #endif diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp index c27203cd9a..988d88b6df 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp @@ -1,397 +1,397 @@ /* * Copyright (c) 2016 Kapustin Alexey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_stroke_selection_properties.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorPopupAction.h" #include "kis_icon_utils.h" #include "KoID.h" #include "kis_image.h" #include "kis_annotation.h" #include "kis_config.h" #include "kis_signal_compressor.h" #include "widgets/kis_cmb_idlist.h" #include "widgets/squeezedcombobox.h" #include "kis_layer_utils.h" #include #include "kis_canvas_resource_provider.h" #include "KoUnit.h" #include "kis_display_color_converter.h" KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer) : KoDialog(view->mainWindow()) { m_resourceManager = view->mainWindow()->resourceManager(); converter = view->canvasBase()->displayColorConverter(); setButtons(Ok | Cancel); setDefaultButton(Ok); - setCaption(i18n("Stroke selection properties")); + setCaption(i18nc("@title:window", "Stroke Selection Properties")); m_page = new WdgStrokeSelection(this); m_image = image; setMainWidget(m_page); resize(m_page->sizeHint()); QString filterConfig = KisConfig().exportConfiguration("StrokeSelection"); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->fromXML(filterConfig); auto &m_options = m_page->m_options; m_options.color = cfg->getColor("color"); m_options.lineColorSource = cfg->getInt("lineColorSource"); m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource); m_page->colorSelector->setColor(getSelectedColor().toQColor()); m_options.brushSelected = cfg->getBool("useBrush", 0); m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1); m_options._colorFillSource = cfg->getInt("colorFillSource", 0); m_page->fillBox->setCurrentIndex(m_options._colorFillSource); m_options.customColor = cfg->getColor("customColor"); if (m_options._colorFillSource == static_cast(colorFillSource::CustomColor)) { m_page->colorFillSelector->setColor(m_options.customColor.toQColor()); } else { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } m_options.fillColor = cfg->getColor("fillColor"); if (m_options._colorFillSource == static_cast(colorFillSource::None)) { m_page->colorFillSelector->setDisabled(true); } else { m_page->colorFillSelector->setDisabled(false); } m_options.lineSize = cfg->getInt("lineSize", 1); m_page->lineSize->setValue(m_options.lineSize); if (m_options.brushSelected) { m_page->lineSize->setDisabled(true); m_page->fillBox->setDisabled(true); m_page->colorFillSelector->setDisabled(true); m_page->sizeBox->setDisabled(true); } m_options.lineDimension = cfg->getInt("lineDimension", 0); m_page->sizeBox->setCurrentIndex(m_options.lineDimension); connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton())); connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton())); connect(m_page->colorFillSelector, SIGNAL(changed(const QColor&)), SLOT(colorFillChanged(const QColor&))); connect(m_page->colorSelector, SIGNAL(changed(const QColor&)), SLOT(colorChanged(const QColor&))); if (isVectorLayer) { lockVectorLayerFunctions(); } } KisDlgStrokeSelection::~KisDlgStrokeSelection() { auto &m_options = m_page->m_options; m_options.lineSize = m_page->lineSize->value(); m_options.lineDimension = m_page->sizeBox->currentIndex(); m_options.lineColorSource = m_page->lineColorBox->currentIndex(); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("lineSize", m_options.lineSize); cfg->setProperty("colorFillSource", m_options._colorFillSource); cfg->setProperty("useBrush", m_options.brushSelected); cfg->setProperty("lineDimension", m_options.lineDimension); cfg->setProperty("lineColorSource", m_options.lineColorSource); QVariant colorVariant("KoColor"); colorVariant.setValue(m_options.customColor); cfg->setProperty("customColor", colorVariant); QVariant _colorVariant("KoColor"); _colorVariant.setValue(m_options.color); cfg->setProperty("color", _colorVariant); QVariant _cVariant("KoColor"); _cVariant.setValue(m_options.fillColor); cfg->setProperty("fillColor", _cVariant); KisConfig().setExportConfiguration("StrokeSelection", cfg); delete m_page; } KoColor KisDlgStrokeSelection::getSelectedColor() const { KoColor color; QString currentSource = m_page->lineColorBox->currentText(); if (currentSource == "Foreground color") { color = m_resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value(); } else if (currentSource == "Background color") { color = m_resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value(); } else { color = m_page->m_options.color; } return color; } KoColor KisDlgStrokeSelection::getFillSelectedColor() const { KoColor color; colorFillSource currentSource = static_cast(m_page->fillBox->currentIndex()); if (currentSource == colorFillSource::FGColor) { color = m_resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value(); } else if (currentSource == colorFillSource::BGColor) { color = m_resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value(); } else if (currentSource == colorFillSource::PaintColor) { color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); } else { color = m_page->m_options.customColor; } return color; } bool KisDlgStrokeSelection::isBrushSelected() const { int index = m_page->typeBox->currentIndex(); drawType type = static_cast(index); if (type == drawType::brushDraw){ return true; } else { return false; } } StrokeSelectionOptions KisDlgStrokeSelection::getParams() const { StrokeSelectionOptions params; params.lineSize = getLineSize(); params.color = getSelectedColor(); params.brushSelected = isBrushSelected(); params.fillColor = getFillSelectedColor(); params._colorFillSource = m_page->m_options._colorFillSource; return params; } void KisDlgStrokeSelection::lockVectorLayerFunctions() { m_page->colorFillSelector->setEnabled(false); m_page->lineSize->setEnabled(false); m_page->sizeBox->setEnabled(false); m_page->fillBox->setEnabled(false); m_page->typeBox->setEnabled(false); } void KisDlgStrokeSelection::unlockVectorLayerFunctions() { m_page->colorFillSelector->setEnabled(true); m_page->lineSize->setEnabled(true); m_page->sizeBox->setEnabled(true); m_page->fillBox->setEnabled(true); m_page->typeBox->setEnabled(true); } void KisDlgStrokeSelection::setColorFillButton() { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } void KisDlgStrokeSelection::setColorButton() { m_page->colorSelector->setColor(getSelectedColor().toQColor()); } int KisDlgStrokeSelection::getLineSize() const { int value = m_page->lineSize->value(); if (m_page->sizeBox->currentText() == "px") { return value; } else if (m_page->sizeBox->currentText() == "mm"){ int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); return pixels; } else { int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); return pixels; } } linePosition KisDlgStrokeSelection::getLinePosition() const {/* TODO int index = m_page->linePosition->currentIndex(); switch(index) { case(0): return linePosition::OUTSIDE; case(1): return linePosition::INSIDE; case(2): return linePosition::CENTER; default: return linePosition::CENTER; }*/ return linePosition::CENTER; } void KisDlgStrokeSelection::colorChanged(const QColor &newColor) { if (m_page->fillBox->currentText() == "Paint color") { m_page->colorFillSelector->setColor(newColor); } QColor BGColor = m_resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == BGColor) && !(newColor == FGColor)) { m_page->m_options.color = tempColor; m_page->lineColorBox->setCurrentIndex(2); //custom color } } void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor) { QColor PaintColor = m_page->colorSelector->color(); QColor BGcolor = m_resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) { m_page->m_options.customColor = tempColor; m_page->fillBox->setCurrentIndex(static_cast(colorFillSource::CustomColor)); } m_page->m_options.fillColor = tempColor; } WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent) { setupUi(this); } void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index) { if (index == static_cast(colorFillSource::None)) { colorFillSelector->setDisabled(true); } else { colorFillSelector->setDisabled(false); emit colorFillSelectorChanged(); } m_options._colorFillSource = index; } void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1) { if (arg1 == "Current Brush") { m_options.brushSelected = true; lineSize->setDisabled(true); fillBox->setDisabled(true); colorFillSelector->setDisabled(true); sizeBox->setDisabled(true); } else { m_options.brushSelected = false; lineSize->setDisabled(false); fillBox->setDisabled(false); colorFillSelector->setDisabled(false); sizeBox->setDisabled(false); } } void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/) { emit colorSelectorChanged(); } StrokeSelectionOptions ::StrokeSelectionOptions(): lineSize(1), brushSelected(false), _colorFillSource(0), lineColorSource(0), lineDimension(0) { color.fromQColor(Qt::black); fillColor.fromQColor(Qt::black); customColor.fromQColor(Qt::black); } KisPainter::FillStyle StrokeSelectionOptions::fillStyle() const { colorFillSource tempColor = static_cast(_colorFillSource); KisPainter::FillStyle style; switch (tempColor) { case colorFillSource::PaintColor: style = KisPainter::FillStyleForegroundColor; break; case colorFillSource::BGColor: style = KisPainter::FillStyleBackgroundColor; break; case colorFillSource::CustomColor: style = KisPainter::FillStyleBackgroundColor; break; case colorFillSource::None: style = KisPainter::FillStyleNone; break; case colorFillSource::FGColor: style = KisPainter::FillStyleBackgroundColor; break; default: style = KisPainter::FillStyleBackgroundColor; } return style; } diff --git a/libs/ui/forms/wdgpaintopsettings.ui b/libs/ui/forms/wdgpaintopsettings.ui index 272622328f..6b523533bc 100644 --- a/libs/ui/forms/wdgpaintopsettings.ui +++ b/libs/ui/forms/wdgpaintopsettings.ui @@ -1,339 +1,569 @@ WdgPaintOpSettings 0 0 - 1175 - 107 + 1405 + 512 - - + + Brush Editor + + + + + + + 0 + + + 1 + + + + + + 0 + 4 + + + + + 120 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 20 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + Erase mode will use a separate brush size + + + Eraser switch size + + + false + + + + + + + + 0 + 0 + + + + Temporarily Save Tweaks To Presets + + + true + + + + + + + + 0 + 0 + + + + Erase mode will use a separate brush opacity + + + Eraser switch opacity + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 60 + 0 + + + + + + + + + + 10 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + Load Engine Defaults + + + + + + + + 0 + 0 + + + + Reload Preset + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + Current Brush Preset. Typed in italic when the preset has unsaved settings. + + + true + + + + + + + + 0 + 0 + + + + Save the current brush settings under this name + + + &Save to Presets + + + + + + + + + true - + 6 0 0 0 0 QFrame::StyledPanel QFrame::Raised - + 0 0 250 - 0 + 300 Fill preset area with current icon Fill area with gradient Fill area with background color Reset area to white - - - - - 0 - 4 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - - - 0 - - - 0 - + + - + - + 0 0 - 0 - 0 + 35 + 20 - - - 16777215 - 60 - + + - - - - - Name: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Current Brush Preset. Typed in italic when the preset has unsaved settings. - - - - - - - Save the current brush settings under this name - - - &Save to Presets - - - - - - - Reload - - - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - - - 0 - 0 - - - - Default preset - - - - - + - + 0 0 - - Temporarily Save Tweaks To Presets + + + 35 + 30 + - - true + + Settings - + - + 0 0 - - Erase mode will use a separate brush size + + + 35 + 30 + - - Eraser switch size + + Scratchpad - - false + + true - - - - 0 - 0 - - - - Erase mode will use a separate brush opacity + + + + 0 + 0 + - Eraser switch opacity + Current Brush Name + + + 0 - - false + + 20 - - + + + Qt::Horizontal + + - 60 - 0 + 40 + 20 - + + + + + + + + + + + 0 + 0 + + + + Engine: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + View + + + + + + + + + + 0 + 0 + + + + + 0 + 350 + + + + + 1677215 + 1677215 + + + + + + + + presetsContainer + brushEditorSettingsControls + scratchpadControls + KisScratchPad QWidget
kis_scratch_pad.h
1
KisPresetSelectorStrip QWidget
kis_preset_selector_strip.h
1
- KisPaintOpListWidget + KisLodAvailabilityWidget QWidget -
kis_paintop_list_widget.h
+
kis_lod_availability_widget.h
1
- KisLodAvailabilityWidget + KisHighlightedToolButton QWidget -
kis_lod_availability_widget.h
+
kis_highlighted_button.h
1
diff --git a/libs/ui/kis_action.cpp b/libs/ui/kis_action.cpp index 9cc5da7869..48f2854a68 100644 --- a/libs/ui/kis_action.cpp +++ b/libs/ui/kis_action.cpp @@ -1,159 +1,147 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_action.h" #include "kis_action_manager.h" #include class Q_DECL_HIDDEN KisAction::Private { public: Private() : flags(NONE), conditions(NO_CONDITION), actionManager(0) {} ActivationFlags flags; ActivationConditions conditions; QStringList excludedNodeTypes; QString operationID; KisActionManager* actionManager; }; KisAction::KisAction(QObject* parent) : QWidgetAction(parent) , d(new Private) { connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::KisAction(const QString& text, QObject* parent) : QWidgetAction(parent) , d(new KisAction::Private) { QAction::setText(text); connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::KisAction(const QIcon &icon, const QString& text, QObject* parent) : QWidgetAction(parent) , d(new Private) { QAction::setIcon(icon); QAction::setText(text); connect(this, SIGNAL(changed()), SLOT(slotChanged())); } KisAction::~KisAction() { delete d; } -KisAction *makeKisAction(QString name, QObject *parent) -{ - KisAction* a = new KisAction(parent); - KisActionRegistry::instance()->propertizeAction(name, a); - KisActionRegistry::instance()->addAction(name, a); - - // TODO: Add other static data (activationFlags, etc.) using getActionXml() - - return a; -} - - // Using a dynamic QObject property is done for compatibility with KAction and // XmlGui. We may merge KisAction into the XmlGui code to make this unnecessary, // but that is probably a lot of work for little benefit. We currently store a // single default shortcut, but the old system used a list (to store default // primary/alternate shortcuts for local and global settings) so we marshal it // for compatibility. void KisAction::setDefaultShortcut(const QKeySequence &shortcut) { QList listifiedShortcut; // Use the empty list to represent no shortcut if (shortcut != QKeySequence("")) { listifiedShortcut.append(shortcut); } setProperty("defaultShortcuts", qVariantFromValue(listifiedShortcut)); } QKeySequence KisAction::defaultShortcut() const { auto listifiedShortcut = property("defaultShortcuts").value >(); if (listifiedShortcut.isEmpty()) { return QKeySequence(); } else { return listifiedShortcut.first(); } } void KisAction::setActivationFlags(KisAction::ActivationFlags flags) { d->flags = flags; } KisAction::ActivationFlags KisAction::activationFlags() { return d->flags; } void KisAction::setActivationConditions(KisAction::ActivationConditions conditions) { d->conditions = conditions; } KisAction::ActivationConditions KisAction::activationConditions() { return d->conditions; } void KisAction::setExcludedNodeTypes(const QStringList &nodeTypes) { d->excludedNodeTypes = nodeTypes; } const QStringList& KisAction::excludedNodeTypes() const { return d->excludedNodeTypes; } void KisAction::setActionEnabled(bool enabled) { setEnabled(enabled); } void KisAction::setActionManager(KisActionManager* actionManager) { d->actionManager = actionManager; } void KisAction::setOperationID(const QString& id) { d->operationID = id; connect(this, SIGNAL(triggered()), this, SLOT(slotTriggered())); } void KisAction::slotTriggered() { if (d->actionManager && !d->operationID.isEmpty()) { d->actionManager->runOperation(d->operationID); } } void KisAction::slotChanged() { emit sigEnableSlaves(isEnabled()); } diff --git a/libs/ui/kis_action.h b/libs/ui/kis_action.h index 24cffc19b5..901b73ed24 100644 --- a/libs/ui/kis_action.h +++ b/libs/ui/kis_action.h @@ -1,136 +1,131 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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_ACTION_H #define KIS_ACTION_H #include #include #include #include class KisActionManager; /** * KisAction, inheriting from QWidgetAction, is a convenience class for GUI * actions, with Krita's configuration system and GUI states. A widget like a * "save" button may be enabled/disabled, hidden or shown depending on the * state of the application, e.g. whether the image currently being viewed was * modified since it was opened. * * Copies of these actions are created for each MainWindow instance. They are * owned by a KisActionManager, of which there is one for each MainWindow. Most * of these instantiations happen inside the constructor for KisMainWindow as * well as the various functions called in KisViewManager::setupManagers(). * **/ class KRITAUI_EXPORT KisAction : public QWidgetAction { Q_OBJECT public: /** * If you re-order these, you must change the associated values in * krita.action and kritamenu.action! */ enum ActivationFlag { NONE = 0x0000, ///< Always activate ACTIVE_IMAGE = 0x0001, ///< Activate if there is at least one image MULTIPLE_IMAGES = 0x0002, ///< Activate if there is more than one image open CURRENT_IMAGE_MODIFIED = 0x0004, ///< Activate if the current image is modified ACTIVE_NODE = 0x0008, ///< Activate if there's an active node (layer or mask) ACTIVE_DEVICE = 0x0010, ///< Activate if the active node has a paint device, i.e. there are pixels to be modified ACTIVE_LAYER = 0x0020, ///< Activate if the current node is a layer (vector or pixel) ACTIVE_TRANSPARENCY_MASK = 0x0040, ///< Activate if the current node is a transparency mask ACTIVE_SHAPE_LAYER = 0x0080, ///< Activate if the current node is a vector layer PIXELS_SELECTED = 0x0100, ///< Activate if there is an active pixel selection SHAPES_SELECTED = 0x0200, ///< Activate if there is an active vector selection PIXEL_SELECTION_WITH_PIXELS = 0x0400, ///< ??? PIXELS_IN_CLIPBOARD = 0x0800, ///< Activate if the clipboard contains pixels SHAPES_IN_CLIPBOARD = 0x1000, ///< Activate if the clipboard contains vector data NEVER_ACTIVATE = 0x2000, ///< ??? LAYERS_IN_CLIPBOARD = 0x4000, ///< ??? IMAGE_HAS_ANIMATION = 0x8000, ///< Activate if the image has an animation }; Q_DECLARE_FLAGS(ActivationFlags, ActivationFlag) enum ActivationCondition { NO_CONDITION = 0, ACTIVE_NODE_EDITABLE = 0x1, ACTIVE_NODE_EDITABLE_PAINT_DEVICE = 0x2, SELECTION_EDITABLE = 0x4 }; Q_DECLARE_FLAGS(ActivationConditions, ActivationCondition) explicit KisAction(QObject* parent = 0); KisAction(const QString& text, QObject* parent = 0); KisAction(const QIcon& icon, const QString& text, QObject* parent = 0); virtual ~KisAction(); - /** - * Produces a new KisAction based on .action data files. - */ - static KisAction *makeKisAction(QString name, QObject *parent); - void setDefaultShortcut(const QKeySequence & shortcut); QKeySequence defaultShortcut() const; void setActivationFlags(ActivationFlags flags); ActivationFlags activationFlags(); void setActivationConditions(ActivationConditions conditions); ActivationConditions activationConditions(); void setExcludedNodeTypes(const QStringList &nodeTypes); const QStringList& excludedNodeTypes() const; virtual void setActionEnabled(bool enabled); /** * Set operation id. This will used to run an operation in the KisActionManager */ void setOperationID(const QString& id); Q_SIGNALS: void sigEnableSlaves(bool value); private Q_SLOTS: void slotTriggered(); void slotChanged(); private: friend class KisActionManager; /** * Set the action manager. Only used by KisActionManager */ void setActionManager(KisActionManager* actionManager); class Private; Private* const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisAction::ActivationFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(KisAction::ActivationConditions) #endif // KIS_ACTION_H diff --git a/libs/ui/kis_action_manager.cpp b/libs/ui/kis_action_manager.cpp index a6fe9d8004..741c5773e0 100644 --- a/libs/ui/kis_action_manager.cpp +++ b/libs/ui/kis_action_manager.cpp @@ -1,466 +1,482 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_action_manager.h" #include #include #include #include "KisPart.h" #include "kis_action.h" #include "KisViewManager.h" #include "kis_selection_manager.h" #include "operations/kis_operation_ui_factory.h" #include "operations/kis_operation_registry.h" #include "operations/kis_operation.h" #include "kis_layer.h" #include "KisDocument.h" #include "kis_clipboard.h" #include #include "QFile" #include #include #include "QFile" #include #include class Q_DECL_HIDDEN KisActionManager::Private { public: Private() : viewManager(0) {} ~Private() { qDeleteAll(uiRegistry.values()); } KisViewManager* viewManager; + KActionCollection *actionCollection; + QList actions; KoGenericRegistry uiRegistry; KisOperationRegistry operationRegistry; }; -KisActionManager::KisActionManager(KisViewManager* viewManager) +KisActionManager::KisActionManager(KisViewManager* viewManager, KActionCollection *actionCollection) : d(new Private) { d->viewManager = viewManager; + d->actionCollection = actionCollection; - + connect(d->actionCollection, + SIGNAL(inserted(QAction*)), SLOT(slotActionAddedToCollection(QAction*))); } KisActionManager::~KisActionManager() { #if 0 if ((d->actions.size() > 0)) { QDomDocument doc; QDomElement e = doc.createElement("Actions"); e.setAttribute("version", "2"); doc.appendChild(e); Q_FOREACH (KisAction *action, d->actions) { QDomElement a = doc.createElement("Action"); a.setAttribute("name", action->objectName()); // But seriously, XML is the worst format ever designed auto addElement = [&](QString title, QString content) { QDomElement newNode = doc.createElement(title); QDomText newText = doc.createTextNode(content); newNode.appendChild(newText); a.appendChild(newNode); }; addElement("icon", action->icon().name()); addElement("text", action->text()); addElement("whatsThis" , action->whatsThis()); addElement("toolTip" , action->toolTip()); addElement("iconText" , action->iconText()); addElement("shortcut" , action->shortcut().toString()); addElement("activationFlags" , QString::number(action->activationFlags(),2));; addElement("activationConditions" , QString::number(action->activationConditions(),2)); addElement("defaultShortcut" , action->defaultShortcut().toString()); addElement("isCheckable" , QString((action->isChecked() ? "true" : "false"))); addElement("statusTip", action->statusTip()); e.appendChild(a); } QFile f("ActionManager.action"); f.open(QFile::WriteOnly); f.write(doc.toString().toUtf8()); f.close(); } #endif delete d; } void KisActionManager::setView(QPointer imageView) { Q_UNUSED(imageView); } +void KisActionManager::slotActionAddedToCollection(QAction *action) +{ + /** + * Small hack alert: not all the actions are still created by the manager and + * immediately added to the action collection. Some plugins add actions + * directly to the action collection when a document is created. Here we + * catch these cases + */ + + KisActionRegistry::instance()->updateShortcut(action->objectName(), action); +} + void KisActionManager::addAction(const QString& name, KisAction* action) { Q_ASSERT(!name.isEmpty()); Q_ASSERT(action); Q_ASSERT(d->viewManager); - Q_ASSERT(d->viewManager->actionCollection()); + Q_ASSERT(d->actionCollection); + + d->actionCollection->addAction(name, action); + action->setParent(d->actionCollection); - d->viewManager->actionCollection()->addAction(name, action); - action->setObjectName(name); - action->setParent(d->viewManager->actionCollection()); - d->viewManager->actionCollection()->setDefaultShortcut(action, action->defaultShortcut()); d->actions.append(action); action->setActionManager(this); - KisActionRegistry::instance()->addAction(name, action); } void KisActionManager::takeAction(KisAction* action) { d->actions.removeOne(action); if (!action->objectName().isEmpty()) { - KIS_ASSERT_RECOVER_RETURN(d->viewManager->actionCollection()); - d->viewManager->actionCollection()->takeAction(action); + KIS_ASSERT_RECOVER_RETURN(d->actionCollection); + d->actionCollection->takeAction(action); } } KisAction *KisActionManager::actionByName(const QString &name) const { Q_FOREACH (KisAction *action, d->actions) { if (action->objectName() == name) { return action; } } return 0; } KisAction *KisActionManager::createAction(const QString &name) { KisAction *a = actionByName(name); // Check if the action already exists if (a) { return a; } // There is some tension here. KisActionManager is supposed to be in control // of global actions, but these actions are supposed to be duplicated. We // will add them to the KisActionRegistry for the time being so we can get // properly categorized shortcuts. a = new KisAction(); - auto actionRegistry = KisActionRegistry::instance(); + KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // Add extra properties actionRegistry->propertizeAction(name, a); - actionRegistry->addAction(name, a); bool ok; // We will skip this check int activationFlags = actionRegistry->getActionProperty(name, "activationFlags").toInt(&ok, 2); int activationConditions = actionRegistry->getActionProperty(name, "activationConditions").toInt(&ok, 2); a->setActivationFlags((KisAction::ActivationFlags) activationFlags); a->setActivationConditions((KisAction::ActivationConditions) activationConditions); addAction(name, a); return a; } void KisActionManager::updateGUI() { //TODO other flags KisAction::ActivationFlags flags; KisImageWSP image; KisNodeSP node; KisLayerSP layer; KisPaintDeviceSP device; KisDocument* document = 0; KisSelectionManager* selectionManager = 0; KisAction::ActivationConditions conditions = KisAction::NO_CONDITION; if (d->viewManager) { // if there are no views, that means no document is open. // we cannot have nodes (selections), devices, or documents without a view if ( d->viewManager->viewCount() > 0 ) { image = d->viewManager->image(); flags |= KisAction::ACTIVE_IMAGE; if (image && image->animationInterface()->hasAnimation()) { flags |= KisAction::IMAGE_HAS_ANIMATION; } node = d->viewManager->activeNode(); device = d->viewManager->activeDevice(); document = d->viewManager->document(); selectionManager = d->viewManager->selectionManager(); if (d->viewManager->viewCount() > 1) { flags |= KisAction::MULTIPLE_IMAGES; } if (document && document->isModified()) { flags |= KisAction::CURRENT_IMAGE_MODIFIED; } if (device) { flags |= KisAction::ACTIVE_DEVICE; } } } // is there a selection/mask? // you have to have at least one view(document) open for this to be true if (node) { // if a node exists, we know there is an active layer as well flags |= KisAction::ACTIVE_NODE; layer = qobject_cast(node.data()); if (layer) { flags |= KisAction::ACTIVE_LAYER; } if (node->inherits("KisTransparencyMask")) { flags |= KisAction::ACTIVE_TRANSPARENCY_MASK; } if (layer && layer->inherits("KisShapeLayer")) { flags |= KisAction::ACTIVE_SHAPE_LAYER; } if (KisClipboard::instance()->hasLayers()) { flags |= KisAction::LAYERS_IN_CLIPBOARD; } if (selectionManager) { if (selectionManager->havePixelsSelected()) { flags |= KisAction::PIXELS_SELECTED; } if (selectionManager->haveShapesSelected()) { flags |= KisAction::SHAPES_SELECTED; } if (selectionManager->havePixelSelectionWithPixels()) { flags |= KisAction::PIXEL_SELECTION_WITH_PIXELS; } if (selectionManager->havePixelsInClipboard()) { flags |= KisAction::PIXELS_IN_CLIPBOARD; } if (selectionManager->haveShapesInClipboard()) { flags |= KisAction::SHAPES_IN_CLIPBOARD; } } if (node->isEditable(false)) { conditions |= KisAction::ACTIVE_NODE_EDITABLE; } if (node->hasEditablePaintDevice()) { conditions |= KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE; } if (d->viewManager->selectionEditable()) { conditions |= KisAction::SELECTION_EDITABLE; } } // loop through all actions in action manager and determine what should be enabled Q_FOREACH (KisAction* action, d->actions) { bool enable; if (action->activationFlags() == KisAction::NONE) { enable = true; } else { enable = action->activationFlags() & flags; // combine action flags with updateGUI flags } enable = enable && (int)(action->activationConditions() & conditions) == (int)action->activationConditions(); if (node && enable) { Q_FOREACH (const QString &type, action->excludedNodeTypes()) { if (node->inherits(type.toLatin1())) { enable = false; break; } } } action->setActionEnabled(enable); } } KisAction *KisActionManager::createStandardAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member) { QAction *standardAction = KStandardAction::create(actionType, receiver, member, 0); KisAction *action = new KisAction(standardAction->icon(), standardAction->text()); const QList defaultShortcuts = standardAction->property("defaultShortcuts").value >(); const QKeySequence defaultShortcut = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(0); action->setDefaultShortcut(standardAction->shortcut()); #ifdef Q_OS_WIN if (actionType == KStandardAction::SaveAs && defaultShortcuts.isEmpty()) { action->setShortcut(QKeySequence("CTRL+SHIFT+S")); } #endif action->setCheckable(standardAction->isCheckable()); if (action->isCheckable()) { action->setChecked(standardAction->isChecked()); } action->setMenuRole(standardAction->menuRole()); action->setText(standardAction->text()); action->setToolTip(standardAction->toolTip()); if (receiver && member) { if (actionType == KStandardAction::OpenRecent) { QObject::connect(action, SIGNAL(urlSelected(QUrl)), receiver, member); } else if (actionType == KStandardAction::ConfigureToolbars) { QObject::connect(action, SIGNAL(triggered(bool)), receiver, member, Qt::QueuedConnection); } else { QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); } } + KisActionRegistry *actionRegistry = KisActionRegistry::instance(); + actionRegistry->propertizeAction(standardAction->objectName(), action); + addAction(standardAction->objectName(), action); delete standardAction; return action; } void KisActionManager::registerOperationUIFactory(KisOperationUIFactory* factory) { d->uiRegistry.add(factory); } void KisActionManager::registerOperation(KisOperation* operation) { d->operationRegistry.add(operation); } void KisActionManager::runOperation(const QString& id) { KisOperationConfigurationSP config = new KisOperationConfiguration(id); KisOperationUIFactory* uiFactory = d->uiRegistry.get(id); if (uiFactory) { bool gotConfig = uiFactory->fetchConfiguration(d->viewManager, config); if (!gotConfig) { return; } } runOperationFromConfiguration(config); } void KisActionManager::runOperationFromConfiguration(KisOperationConfigurationSP config) { KisOperation* operation = d->operationRegistry.get(config->id()); Q_ASSERT(operation); operation->runFromXML(d->viewManager, *config); } void KisActionManager::dumpActionFlags() { QFile data("actions.txt"); if (data.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&data); out.setCodec("UTF-8"); Q_FOREACH (KisAction* action, d->actions) { KisAction::ActivationFlags flags = action->activationFlags(); out << "-------- " << action->text() << " --------\n"; out << "Action will activate on: \n"; if (flags & KisAction::ACTIVE_IMAGE) { out << " Active image\n"; } if (flags & KisAction::MULTIPLE_IMAGES) { out << " More than one image open\n"; } if (flags & KisAction::CURRENT_IMAGE_MODIFIED) { out << " Active image modified\n"; } if (flags & KisAction::ACTIVE_DEVICE) { out << " Active device\n"; } if (flags & KisAction::ACTIVE_LAYER) { out << " Active layer\n"; } if (flags & KisAction::ACTIVE_TRANSPARENCY_MASK) { out << " Active transparency mask\n"; } if (flags & KisAction::ACTIVE_NODE) { out << " Active node\n"; } if (flags & KisAction::ACTIVE_SHAPE_LAYER) { out << " Active shape layer\n"; } if (flags & KisAction::PIXELS_SELECTED) { out << " Pixels selected\n"; } if (flags & KisAction::SHAPES_SELECTED) { out << " Shapes selected\n"; } if (flags & KisAction::PIXEL_SELECTION_WITH_PIXELS) { out << " Pixel selection with pixels\n"; } if (flags & KisAction::PIXELS_IN_CLIPBOARD) { out << " Pixels in clipboard\n"; } if (flags & KisAction::SHAPES_IN_CLIPBOARD) { out << " Shape in clipboard\n"; } if (flags & KisAction::IMAGE_HAS_ANIMATION) { out << " Image has animation\n"; } out << "\n\n"; out << "Action will only activate if the following conditions are met: \n"; KisAction::ActivationConditions conditions = action->activationConditions(); if ((int)conditions == 0) { out << " -\n"; } if (conditions & KisAction::ACTIVE_NODE_EDITABLE) { out << " Active Node editable\n"; } if (conditions & KisAction::ACTIVE_NODE_EDITABLE_PAINT_DEVICE) { out << " Active Node has editable paint device\n"; } if (conditions & KisAction::SELECTION_EDITABLE) { out << " Selection is editable\n"; } out << "\n\n"; } } } diff --git a/libs/ui/kis_action_manager.h b/libs/ui/kis_action_manager.h index 40aaa27ef1..c796433e0f 100644 --- a/libs/ui/kis_action_manager.h +++ b/libs/ui/kis_action_manager.h @@ -1,110 +1,113 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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_ACTION_MANAGER_H #define KIS_ACTION_MANAGER_H #include #include #include "KisView.h" #include "kstandardaction.h" #include "kis_action_registry.h" #include "operations/kis_operation_configuration.h" class KisViewManager; class KisAction; class KisOperationUIFactory; class KisOperation; /** * @brief A KisActionManager class keeps track of KisActions. * These actions are always associated with the GUI. That means each MainWindow * will create its own duplicate of these actions. * * KisActionManager enables and disables actions, to grey out buttons according * to the state of the application. * * Some of the primitive actions (load/save and so on) are not defined as * KisActions, but instead KActions, automacially registered through KXMLGUI. * It tracks these actions through the KActionCollection owned by the window. * Ultimately it would be nice to unify these things more fully. * */ -class KRITAUI_EXPORT KisActionManager +class KRITAUI_EXPORT KisActionManager : public QObject { - + Q_OBJECT public: - KisActionManager(KisViewManager* viewManager); + KisActionManager(KisViewManager* viewManager, KActionCollection *actionCollection); virtual ~KisActionManager(); void setView(QPointer imageView); /** * Add an existing action to the action manager. */ void addAction(const QString& name, KisAction* action); /** * Stop managing an action. */ void takeAction(KisAction* action); /** * Create a new KisAction. Looks up data from the .action data files. */ KisAction *createAction(const QString &name); /** * Look up an action by name. */ KisAction *actionByName(const QString &name) const; void registerOperationUIFactory(KisOperationUIFactory* factory); void registerOperation(KisOperation* operation); void runOperation(const QString &id); void runOperationFromConfiguration(KisOperationConfigurationSP config); /** * Update actions handled by kis_action_manager to set enabled. * This is used to grey out buttons that can't be pressed. */ void updateGUI(); /** * Create a KisAction based on a KStandardAction. The KStandardAction is deleted. */ KisAction *createStandardAction(KStandardAction::StandardAction, const QObject *receiver, const char *member); +private Q_SLOTS: + void slotActionAddedToCollection(QAction *action); + private: void dumpActionFlags(); class Private; Private* const d; }; #endif // KIS_ACTION_MANAGER_H diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 60ed252e37..85925dda1d 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,1768 +1,1788 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include KisConfig::KisConfig() : m_cfg( KSharedConfig::openConfig()->group("")) { } KisConfig::~KisConfig() { if (qApp->thread() != QThread::currentThread()) { //dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping..."; return; } m_cfg.sync(); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col)); } void KisConfig::setMDIBackgroundColor(const QColor &v) const { m_cfg.writeEntry("mdiBackgroundColor", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg; QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); if (bytes.length() > 0) { const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); return profile; } else { //dbgKrita << "\tCould not get a system monitor profile"; return 0; } } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } //dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); QString canvasState = m_cfg.readEntry("canvasState", "OPENGL_SUCCESS"); return (m_cfg.readEntry("useOpenGL", true) && (canvasState == "OPENGL_SUCCESS" || canvasState == "TRY_OPENGL")); } void KisConfig::setUseOpenGL(bool useOpenGL) const { m_cfg.writeEntry("useOpenGL", useOpenGL); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } qint32 KisConfig::maxNumberOfThreads(bool defaultValue) const { return (defaultValue ? QThread::idealThreadCount() : m_cfg.readEntry("maxthreads", QThread::idealThreadCount())); } void KisConfig::setMaxNumberOfThreads(qint32 maxThreads) { m_cfg.writeEntry("maxthreads", maxThreads); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); } qint32 KisConfig::checkSize(bool defaultValue) const { return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); } void KisConfig::setCheckSize(qint32 checksize) const { m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const { QColor def(255, 0, 0, 220); return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def)); } void KisConfig::setSelectionOverlayMaskColor(const QColor &color) { m_cfg.writeEntry("selectionOverlayMaskColor", color); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::hideSplashScreen(bool defaultValue) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true)); } void KisConfig::setHideSplashScreen(bool hideSplashScreen) const { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 300 : m_cfg.readEntry("AutoSaveInterval", 300)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { return (defaultValue ? "OPENGL_NOT_TRIED" : m_cfg.readEntry("canvasState", "OPENGL_NOT_TRIED")); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { m_cfg.writeEntry("canvasState", state); m_cfg.sync(); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } +int KisConfig::presetIconSize(bool defaultValue) const +{ + return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30)); +} + +void KisConfig::setPresetIconSize(const int value) const +{ + m_cfg.writeEntry("presetIconSize", value); +} + bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockerTitleBars(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true)); } void KisConfig::setShowDockerTitleBars(const bool value) const { m_cfg.writeEntry("showDockerTitleBars", value); } +bool KisConfig::showDockers(bool defaultValue) const +{ + return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); +} + +void KisConfig::setShowDockers(const bool value) const +{ + m_cfg.writeEntry("showDockers", value); +} + bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList())); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } QString KisConfig::ocioConfigurationPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString())); } void KisConfig::setOcioConfigurationPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path); } QString KisConfig::ocioLutPath(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString())); } void KisConfig::setOcioLutPath(const QString &path) const { m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString())); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(QColor value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const { return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12)); } void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const { m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } bool KisConfig::useVerboseOpenGLDebugOutput(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useVerboseOpenGLDebugOutput", false)); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLDebugging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLDebugging", false)); } void KisConfig::setEnableOpenGLDebugging(bool value) const { m_cfg.writeEntry("enableOpenGLDebugging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scribbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scribbingUpdatesDelay", 30)); } void KisConfig::setScribbingUpdatesDelay(int value) { m_cfg.writeEntry("scribbingUpdatesDelay", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } int KisConfig::stabilizerDelayedPaintInterval(bool defaultValue) const { const int defaultInterval = 20; return defaultValue ? defaultInterval : m_cfg.readEntry("stabilizerDelayedPaintInterval", defaultInterval); } void KisConfig::setStabilizerDelayedPaintInterval(int value) { m_cfg.writeEntry("stabilizerDelayedPaintInterval", value); } QString KisConfig::customFFMpegPath(bool defaultValue) const { return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString()); } void KisConfig::setCustomFFMpegPath(const QString &value) const { m_cfg.writeEntry("ffmpegExecutablePath", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const { QDomDocument doc; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id(), QHash()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); return KoColor::fromXML(e, Integer16BitsColorDepthID.id(), QHash()); } return color; } diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h index 4f2fede101..bd5181c1ac 100644 --- a/libs/ui/kis_config.h +++ b/libs/ui/kis_config.h @@ -1,530 +1,537 @@ /* * Copyright (c) 2002 Patrick Julien * * 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_CONFIG_H_ #define KIS_CONFIG_H_ #include #include #include #include #include #include #include "kis_global.h" #include "kis_properties_configuration.h" #include "kritaui_export.h" class KoColorProfile; class KoColorSpace; class KisSnapConfig; class KRITAUI_EXPORT KisConfig { public: KisConfig(); ~KisConfig(); bool disableTouchOnCanvas(bool defaultValue = false) const; void setDisableTouchOnCanvas(bool value) const; bool useProjections(bool defaultValue = false) const; void setUseProjections(bool useProj) const; bool undoEnabled(bool defaultValue = false) const; void setUndoEnabled(bool undo) const; int undoStackLimit(bool defaultValue = false) const; void setUndoStackLimit(int limit) const; bool useCumulativeUndoRedo(bool defaultValue = false) const; void setCumulativeUndoRedo(bool value); double stackT1(bool defaultValue = false) const; void setStackT1(int T1); double stackT2(bool defaultValue = false) const; void setStackT2(int T2); int stackN(bool defaultValue = false) const; void setStackN(int N); qint32 defImageWidth(bool defaultValue = false) const; void defImageWidth(qint32 width) const; qint32 defImageHeight(bool defaultValue = false) const; void defImageHeight(qint32 height) const; qreal defImageResolution(bool defaultValue = false) const; void defImageResolution(qreal res) const; /** * @return the id of the default color model used for creating new images. */ QString defColorModel(bool defaultValue = false) const; /** * set the id of the default color model used for creating new images. */ void defColorModel(const QString & model) const; /** * @return the id of the default color depth used for creating new images. */ QString defaultColorDepth(bool defaultValue = false) const; /** * set the id of the default color depth used for creating new images. */ void setDefaultColorDepth(const QString & depth) const; /** * @return the id of the default color profile used for creating new images. */ QString defColorProfile(bool defaultValue = false) const; /** * set the id of the default color profile used for creating new images. */ void defColorProfile(const QString & depth) const; CursorStyle newCursorStyle(bool defaultValue = false) const; void setNewCursorStyle(CursorStyle style); OutlineStyle newOutlineStyle(bool defaultValue = false) const; void setNewOutlineStyle(OutlineStyle style); QRect colorPreviewRect() const; void setColorPreviewRect(const QRect &rect); /// get the profile the user has selected for the given screen QString monitorProfile(int screen) const; void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const; QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const; void setMonitorForScreen(int screen, const QString& monitor); /// Get the actual profile to be used for the given screen, which is /// either the screen profile set by the color management system or /// the custom monitor profile set by the user, depending on the configuration const KoColorProfile *displayProfile(int screen) const; QString workingColorSpace(bool defaultValue = false) const; void setWorkingColorSpace(const QString & workingColorSpace) const; QString importProfile(bool defaultValue = false) const; void setImportProfile(const QString & importProfile) const; QString printerColorSpace(bool defaultValue = false) const; void setPrinterColorSpace(const QString & printerColorSpace) const; QString printerProfile(bool defaultValue = false) const; void setPrinterProfile(const QString & printerProfile) const; bool useBlackPointCompensation(bool defaultValue = false) const; void setUseBlackPointCompensation(bool useBlackPointCompensation) const; bool allowLCMSOptimization(bool defaultValue = false) const; void setAllowLCMSOptimization(bool allowLCMSOptimization); void writeKoColor(const QString& name, const KoColor& color) const; KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const; bool showRulers(bool defaultValue = false) const; void setShowRulers(bool rulers) const; bool rulersTrackMouse(bool defaultValue = false) const; void setRulersTrackMouse(bool value) const; qint32 pasteBehaviour(bool defaultValue = false) const; void setPasteBehaviour(qint32 behaviour) const; qint32 monitorRenderIntent(bool defaultValue = false) const; void setRenderIntent(qint32 monitorRenderIntent) const; bool useOpenGL(bool defaultValue = false) const; void setUseOpenGL(bool useOpenGL) const; int openGLFilteringMode(bool defaultValue = false) const; void setOpenGLFilteringMode(int filteringMode); bool useOpenGLTextureBuffer(bool defaultValue = false) const; void setUseOpenGLTextureBuffer(bool useBuffer); bool disableVSync(bool defaultValue = false) const; void setDisableVSync(bool disableVSync); bool showAdvancedOpenGLSettings(bool defaultValue = false) const; bool forceOpenGLFenceWorkaround(bool defaultValue = false) const; int numMipmapLevels(bool defaultValue = false) const; int openGLTextureSize(bool defaultValue = false) const; int textureOverlapBorder() const; qint32 maxNumberOfThreads(bool defaultValue = false) const; void setMaxNumberOfThreads(qint32 numberOfThreads); quint32 getGridMainStyle(bool defaultValue = false) const; void setGridMainStyle(quint32 v) const; quint32 getGridSubdivisionStyle(bool defaultValue = false) const; void setGridSubdivisionStyle(quint32 v) const; QColor getGridMainColor(bool defaultValue = false) const; void setGridMainColor(const QColor & v) const; QColor getGridSubdivisionColor(bool defaultValue = false) const; void setGridSubdivisionColor(const QColor & v) const; quint32 guidesLineStyle(bool defaultValue = false) const; void setGuidesLineStyle(quint32 v) const; QColor guidesColor(bool defaultValue = false) const; void setGuidesColor(const QColor & v) const; void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const; void saveSnapConfig(const KisSnapConfig &config); qint32 checkSize(bool defaultValue = false) const; void setCheckSize(qint32 checkSize) const; bool scrollCheckers(bool defaultValue = false) const; void setScrollingCheckers(bool scollCheckers) const; QColor checkersColor1(bool defaultValue = false) const; void setCheckersColor1(const QColor & v) const; QColor checkersColor2(bool defaultValue = false) const; void setCheckersColor2(const QColor & v) const; QColor canvasBorderColor(bool defaultValue = false) const; void setCanvasBorderColor(const QColor &color) const; bool hideScrollbars(bool defaultValue = false) const; void setHideScrollbars(bool value) const; bool antialiasCurves(bool defaultValue = false) const; void setAntialiasCurves(bool v) const; QColor selectionOverlayMaskColor(bool defaultValue = false) const; void setSelectionOverlayMaskColor(const QColor &color); bool antialiasSelectionOutline(bool defaultValue = false) const; void setAntialiasSelectionOutline(bool v) const; bool showRootLayer(bool defaultValue = false) const; void setShowRootLayer(bool showRootLayer) const; bool showGlobalSelection(bool defaultValue = false) const; void setShowGlobalSelection(bool showGlobalSelection) const; bool showOutlineWhilePainting(bool defaultValue = false) const; void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const; bool hideSplashScreen(bool defaultValue = false) const; void setHideSplashScreen(bool hideSplashScreen) const; qreal outlineSizeMinimum(bool defaultValue = false) const; void setOutlineSizeMinimum(qreal outlineSizeMinimum) const; int autoSaveInterval(bool defaultValue = false) const; void setAutoSaveInterval(int seconds) const; bool backupFile(bool defaultValue = false) const; void setBackupFile(bool backupFile) const; bool showFilterGallery(bool defaultValue = false) const; void setShowFilterGallery(bool showFilterGallery) const; bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const; void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const; // OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED QString canvasState(bool defaultValue = false) const; void setCanvasState(const QString& state) const; bool toolOptionsPopupDetached(bool defaultValue = false) const; void setToolOptionsPopupDetached(bool detached) const; bool paintopPopupDetached(bool defaultValue = false) const; void setPaintopPopupDetached(bool detached) const; QString pressureTabletCurve(bool defaultValue = false) const; void setPressureTabletCurve(const QString& curveString) const; qreal vastScrolling(bool defaultValue = false) const; void setVastScrolling(const qreal factor) const; int presetChooserViewMode(bool defaultValue = false) const; void setPresetChooserViewMode(const int mode) const; + int presetIconSize(bool defaultValue = false) const; + void setPresetIconSize(const int value) const; + + bool firstRun(bool defaultValue = false) const; void setFirstRun(const bool firstRun) const; bool clicklessSpacePan(bool defaultValue = false) const; void setClicklessSpacePan(const bool toggle) const; int horizontalSplitLines(bool defaultValue = false) const; void setHorizontalSplitLines(const int numberLines) const; int verticalSplitLines(bool defaultValue = false) const; void setVerticalSplitLines(const int numberLines) const; bool hideDockersFullscreen(bool defaultValue = false) const; void setHideDockersFullscreen(const bool value) const; bool showDockerTitleBars(bool defaultValue = false) const; void setShowDockerTitleBars(const bool value) const; + bool showDockers(bool defaultValue = false) const; + void setShowDockers(const bool value) const; + bool showStatusBar(bool defaultValue = false) const; void setShowStatusBar(const bool value) const; bool hideMenuFullscreen(bool defaultValue = false) const; void setHideMenuFullscreen(const bool value) const; bool hideScrollbarsFullscreen(bool defaultValue = false) const; void setHideScrollbarsFullscreen(const bool value) const; bool hideStatusbarFullscreen(bool defaultValue = false) const; void setHideStatusbarFullscreen(const bool value) const; bool hideTitlebarFullscreen(bool defaultValue = false) const; void setHideTitlebarFullscreen(const bool value) const; bool hideToolbarFullscreen(bool defaultValue = false) const; void setHideToolbarFullscreen(const bool value) const; bool fullscreenMode(bool defaultValue = false) const; void setFullscreenMode(const bool value) const; QStringList favoriteCompositeOps(bool defaultValue = false) const; void setFavoriteCompositeOps(const QStringList& compositeOps) const; QString exportConfiguration(const QString &filterId, bool defaultValue = false) const; void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const; bool useOcio(bool defaultValue = false) const; void setUseOcio(bool useOCIO) const; int favoritePresets(bool defaultValue = false) const; void setFavoritePresets(const int value); bool levelOfDetailEnabled(bool defaultValue = false) const; void setLevelOfDetailEnabled(bool value); enum OcioColorManagementMode { INTERNAL = 0, OCIO_CONFIG, OCIO_ENVIRONMENT }; OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const; void setOcioColorManagementMode(OcioColorManagementMode mode) const; QString ocioConfigurationPath(bool defaultValue = false) const; void setOcioConfigurationPath(const QString &path) const; QString ocioLutPath(bool defaultValue = false) const; void setOcioLutPath(const QString &path) const; int ocioLutEdgeSize(bool defaultValue = false) const; void setOcioLutEdgeSize(int value); bool ocioLockColorVisualRepresentation(bool defaultValue = false) const; void setOcioLockColorVisualRepresentation(bool value); bool useSystemMonitorProfile(bool defaultValue = false) const; void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const; QString defaultPalette(bool defaultValue = false) const; void setDefaultPalette(const QString& name) const; QString toolbarSlider(int sliderNumber, bool defaultValue = false) const; void setToolbarSlider(int sliderNumber, const QString &slider); bool sliderLabels(bool defaultValue = false) const; void setSliderLabels(bool enabled); QString currentInputProfile(bool defaultValue = false) const; void setCurrentInputProfile(const QString& name); bool presetStripVisible(bool defaultValue = false) const; void setPresetStripVisible(bool visible); bool scratchpadVisible(bool defaultValue = false) const; void setScratchpadVisible(bool visible); bool showSingleChannelAsColor(bool defaultValue = false) const; void setShowSingleChannelAsColor(bool asColor); bool hidePopups(bool defaultValue = false) const; void setHidePopups(bool hidepopups); int numDefaultLayers(bool defaultValue = false) const; void setNumDefaultLayers(int num); quint8 defaultBackgroundOpacity(bool defaultValue = false) const; void setDefaultBackgroundOpacity(quint8 value); QColor defaultBackgroundColor(bool defaultValue = false) const; void setDefaultBackgroundColor(QColor value); enum BackgroundStyle { LAYER = 0, PROJECTION = 1 }; BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const; void setDefaultBackgroundStyle(BackgroundStyle value); int lineSmoothingType(bool defaultValue = false) const; void setLineSmoothingType(int value); qreal lineSmoothingDistance(bool defaultValue = false) const; void setLineSmoothingDistance(qreal value); qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const; void setLineSmoothingTailAggressiveness(qreal value); bool lineSmoothingSmoothPressure(bool defaultValue = false) const; void setLineSmoothingSmoothPressure(bool value); bool lineSmoothingScalableDistance(bool defaultValue = false) const; void setLineSmoothingScalableDistance(bool value); qreal lineSmoothingDelayDistance(bool defaultValue = false) const; void setLineSmoothingDelayDistance(qreal value); bool lineSmoothingUseDelayDistance(bool defaultValue = false) const; void setLineSmoothingUseDelayDistance(bool value); bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const; void setLineSmoothingFinishStabilizedCurve(bool value); bool lineSmoothingStabilizeSensors(bool defaultValue = false) const; void setLineSmoothingStabilizeSensors(bool value); int paletteDockerPaletteViewSectionSize(bool defaultValue = false) const; void setPaletteDockerPaletteViewSectionSize(int value) const; int tabletEventsDelay(bool defaultValue = false) const; void setTabletEventsDelay(int value); bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const; void setTestingAcceptCompressedTabletEvents(bool value); bool shouldEatDriverShortcuts(bool defaultValue = false) const; bool testingCompressBrushEvents(bool defaultValue = false) const; void setTestingCompressBrushEvents(bool value); const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const; void setCustomColorSelectorColorSpace(const KoColorSpace *cs); bool useDirtyPresets(bool defaultValue = false) const; void setUseDirtyPresets(bool value); bool useEraserBrushSize(bool defaultValue = false) const; void setUseEraserBrushSize(bool value); bool useEraserBrushOpacity(bool defaultValue = false) const; void setUseEraserBrushOpacity(bool value); QColor getMDIBackgroundColor(bool defaultValue = false) const; void setMDIBackgroundColor(const QColor & v) const; QString getMDIBackgroundImage(bool defaultValue = false) const; void setMDIBackgroundImage(const QString & fileName) const; bool useVerboseOpenGLDebugOutput(bool defaultValue = false) const; int workaroundX11SmoothPressureSteps(bool defaultValue = false) const; bool showCanvasMessages(bool defaultValue = false) const; void setShowCanvasMessages(bool show); bool compressKra(bool defaultValue = false) const; void setCompressKra(bool compress); bool toolOptionsInDocker(bool defaultValue = false) const; void setToolOptionsInDocker(bool inDocker); void setEnableOpenGLDebugging(bool value) const; bool enableOpenGLDebugging(bool defaultValue = false) const; void setEnableAmdVectorizationWorkaround(bool value); bool enableAmdVectorizationWorkaround(bool defaultValue = false) const; bool animationDropFrames(bool defaultValue = false) const; void setAnimationDropFrames(bool value); int scribbingUpdatesDelay(bool defaultValue = false) const; void setScribbingUpdatesDelay(int value); bool switchSelectionCtrlAlt(bool defaultValue = false) const; void setSwitchSelectionCtrlAlt(bool value); bool convertToImageColorspaceOnImport(bool defaultValue = false) const; void setConvertToImageColorspaceOnImport(bool value); int stabilizerSampleSize(bool defaultValue = false) const; void setStabilizerSampleSize(int value); int stabilizerDelayedPaintInterval(bool defaultValue = false) const; void setStabilizerDelayedPaintInterval(int value); QString customFFMpegPath(bool defaultValue = false) const; void setCustomFFMpegPath(const QString &value) const; bool showBrushHud(bool defaultValue = false) const; void setShowBrushHud(bool value); QString brushHudSetting(bool defaultValue = false) const; void setBrushHudSetting(const QString &value) const; template void writeEntry(const QString& name, const T& value) { m_cfg.writeEntry(name, value); } template void writeList(const QString& name, const QList& value) { m_cfg.writeEntry(name, value); } template T readEntry(const QString& name, const T& defaultValue=T()) { return m_cfg.readEntry(name, defaultValue); } template QList readList(const QString& name, const QList& defaultValue=QList()) { return m_cfg.readEntry(name, defaultValue); } /// get the profile the color managment system has stored for the given screen static const KoColorProfile* getScreenProfile(int screen); private: KisConfig(const KisConfig&); KisConfig& operator=(const KisConfig&) const; private: mutable KConfigGroup m_cfg; }; #endif // KIS_CONFIG_H_ diff --git a/libs/ui/kis_filter_manager.cc b/libs/ui/kis_filter_manager.cc index 52b7df81ba..541d5edd71 100644 --- a/libs/ui/kis_filter_manager.cc +++ b/libs/ui/kis_filter_manager.cc @@ -1,348 +1,350 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_filter_manager.h" #include #include #include #include #include #include #include // krita/image #include #include #include #include // krita/ui #include "KisViewManager.h" #include "kis_canvas2.h" #include #include "kis_action.h" #include "kis_action_manager.h" #include "kis_canvas_resource_provider.h" #include "dialogs/kis_dlg_filter.h" #include "strokes/kis_filter_stroke_strategy.h" #include "krita_utils.h" struct KisFilterManager::Private { Private() : reapplyAction(0) , actionCollection(0) , actionManager(0) , view(0) { } KisAction* reapplyAction; QHash filterActionMenus; QHash filters2Action; KActionCollection *actionCollection; KisActionManager *actionManager; KisViewManager *view; KisFilterConfigurationSP lastConfiguration; KisFilterConfigurationSP currentlyAppliedConfiguration; KisStrokeId currentStrokeId; QRect initialApplyRect; QSignalMapper actionsMapper; QPointer filterDialog; }; KisFilterManager::KisFilterManager(KisViewManager * view) : d(new Private) { d->view = view; } KisFilterManager::~KisFilterManager() { delete d; } void KisFilterManager::setView(QPointerimageView) { Q_UNUSED(imageView); } void KisFilterManager::setup(KActionCollection * ac, KisActionManager *actionManager) { d->actionCollection = ac; d->actionManager = actionManager; // Setup reapply action d->reapplyAction = d->actionManager->createAction("filter_apply_again"); d->reapplyAction->setEnabled(false); connect(d->reapplyAction, SIGNAL(triggered()), SLOT(reapplyLastFilter())); connect(&d->actionsMapper, SIGNAL(mapped(const QString&)), SLOT(showFilterDialog(const QString&))); // Setup list of filters QStringList keys = KisFilterRegistry::instance()->keys(); keys.sort(); Q_FOREACH (const QString &filterName, keys) { insertFilter(filterName); } connect(KisFilterRegistry::instance(), SIGNAL(filterAdded(QString)), SLOT(insertFilter(const QString &))); } void KisFilterManager::insertFilter(const QString & filterName) { Q_ASSERT(d->actionCollection); KisFilterSP filter = KisFilterRegistry::instance()->value(filterName); Q_ASSERT(filter); if (d->filters2Action.contains(filter.data())) { warnKrita << "Filter" << filterName << " has already been inserted"; return; } KoID category = filter->menuCategory(); KActionMenu* actionMenu = d->filterActionMenus[ category.id()]; if (!actionMenu) { actionMenu = new KActionMenu(category.name(), this); d->actionCollection->addAction(category.id(), actionMenu); d->filterActionMenus[category.id()] = actionMenu; } KisAction *action = new KisAction(filter->menuEntry(), this); action->setDefaultShortcut(filter->shortcut()); action->setActivationFlags(KisAction::ACTIVE_DEVICE); d->actionManager->addAction(QString("krita_filter_%1").arg(filterName), action); d->filters2Action[filter.data()] = action; actionMenu->addAction(action); d->actionsMapper.setMapping(action, filterName); connect(action, SIGNAL(triggered()), &d->actionsMapper, SLOT(map())); } void KisFilterManager::updateGUI() { if (!d->view) return; bool enable = false; KisNodeSP activeNode = d->view->activeNode(); enable = activeNode && activeNode->hasEditablePaintDevice(); d->reapplyAction->setEnabled(enable); for (QHash::iterator it = d->filters2Action.begin(); it != d->filters2Action.end(); ++it) { bool localEnable = enable; it.value()->setEnabled(localEnable); } } void KisFilterManager::reapplyLastFilter() { if (!d->lastConfiguration) return; apply(d->lastConfiguration); finish(); } void KisFilterManager::showFilterDialog(const QString &filterId) { if (d->filterDialog && d->filterDialog->isVisible()) { KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); d->filterDialog->setFilter(filter); return; } connect(d->view->image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(slotStrokeCancelRequested()), Qt::UniqueConnection); connect(d->view->image(), SIGNAL(sigStrokeEndRequested()), SLOT(slotStrokeEndRequested()), Qt::UniqueConnection); /** * The UI should show only after every running stroke is finished, * so a virtual barrier is added here. */ - d->view->image()->waitForDone(); + if (!d->view->blockUntillOperationsFinished(d->view->image())) { + return; + } Q_ASSERT(d->view); Q_ASSERT(d->view->activeNode()); KisPaintDeviceSP dev = d->view->activeNode()->paintDevice(); if (!dev) { warnKrita << "KisFilterManager::showFilterDialog(): Filtering was requested for illegal active layer!" << d->view->activeNode(); return; } KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); if (dev->colorSpace()->willDegrade(filter->colorSpaceIndependence())) { // Warning bells! if (filter->colorSpaceIndependence() == TO_LAB16) { if (QMessageBox::warning(d->view->mainWindow(), i18nc("@title:window", "Krita"), i18n("The %1 filter will convert your %2 data to 16-bit L*a*b* and vice versa. ", filter->name(), dev->colorSpace()->name()), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) return; } else if (filter->colorSpaceIndependence() == TO_RGBA16) { if (QMessageBox::warning(d->view->mainWindow(), i18nc("@title:window", "Krita"), i18n("The %1 filter will convert your %2 data to 16-bit RGBA and vice versa. ", filter->name() , dev->colorSpace()->name()), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) return; } } if (filter->showConfigurationWidget()) { if (!d->filterDialog) { d->filterDialog = new KisDlgFilter(d->view , d->view->activeNode(), this, d->view->mainWindow()); d->filterDialog->setAttribute(Qt::WA_DeleteOnClose); } d->filterDialog->setFilter(filter); d->filterDialog->setVisible(true); } else { apply(KisFilterConfigurationSP(filter->defaultConfiguration(d->view->activeNode()->original()))); finish(); } } void KisFilterManager::apply(KisFilterConfigurationSP filterConfig) { KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); KisImageWSP image = d->view->image(); if (d->currentStrokeId) { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::CancelSilentlyMarker); image->cancelStroke(d->currentStrokeId); d->currentStrokeId.clear(); } else { image->waitForDone(); d->initialApplyRect = d->view->activeNode()->exactBounds(); } QRect applyRect = d->initialApplyRect; KisPaintDeviceSP paintDevice = d->view->activeNode()->paintDevice(); if (paintDevice && filter->needsTransparentPixels(filterConfig.data(), paintDevice->colorSpace())) { applyRect |= image->bounds(); } KoCanvasResourceManager *resourceManager = d->view->resourceProvider()->resourceManager(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, d->view->activeNode(), resourceManager); d->currentStrokeId = image->startStroke(new KisFilterStrokeStrategy(filter, KisFilterConfigurationSP(filterConfig), resources)); QRect processRect = filter->changedRect(applyRect, filterConfig.data(), 0); processRect &= image->bounds(); if (filter->supportsThreading()) { QSize size = KritaUtils::optimalPatchSize(); QVector rects = KritaUtils::splitRectIntoPatches(processRect, size); Q_FOREACH (const QRect &rc, rects) { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::Data(rc, true)); } } else { image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::Data(processRect, false)); } d->currentlyAppliedConfiguration = filterConfig; } void KisFilterManager::finish() { Q_ASSERT(d->currentStrokeId); d->view->image()->endStroke(d->currentStrokeId); KisFilterSP filter = KisFilterRegistry::instance()->value(d->currentlyAppliedConfiguration->name()); if (filter->bookmarkManager()) { filter->bookmarkManager()->save(KisBookmarkedConfigurationManager::ConfigLastUsed, d->currentlyAppliedConfiguration.data()); } d->lastConfiguration = d->currentlyAppliedConfiguration; d->reapplyAction->setEnabled(true); d->reapplyAction->setText(i18n("Apply Filter Again: %1", filter->name())); d->currentStrokeId.clear(); d->currentlyAppliedConfiguration.clear(); } void KisFilterManager::cancel() { Q_ASSERT(d->currentStrokeId); d->view->image()->cancelStroke(d->currentStrokeId); d->currentStrokeId.clear(); d->currentlyAppliedConfiguration.clear(); } bool KisFilterManager::isStrokeRunning() const { return d->currentStrokeId; } void KisFilterManager::slotStrokeEndRequested() { if (d->currentStrokeId && d->filterDialog) { d->filterDialog->accept(); } } void KisFilterManager::slotStrokeCancelRequested() { if (d->currentStrokeId && d->filterDialog) { d->filterDialog->reject(); } } diff --git a/libs/ui/kis_layer_manager.cc b/libs/ui/kis_layer_manager.cc index 0d9b9b97ec..8398bdb375 100644 --- a/libs/ui/kis_layer_manager.cc +++ b/libs/ui/kis_layer_manager.cc @@ -1,831 +1,845 @@ /* * Copyright (C) 2006 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_layer_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisImportExportManager.h" #include "kis_config.h" #include "kis_cursor.h" #include "dialogs/kis_dlg_adj_layer_props.h" #include "dialogs/kis_dlg_adjustment_layer.h" #include "dialogs/kis_dlg_layer_properties.h" #include "dialogs/kis_dlg_generator_layer.h" #include "dialogs/kis_dlg_file_layer.h" #include "dialogs/kis_dlg_layer_style.h" #include "KisDocument.h" #include "kis_filter_manager.h" #include "kis_node_visitor.h" #include "kis_paint_layer.h" #include "commands/kis_image_commands.h" #include "commands/kis_layer_command.h" #include "commands/kis_node_commands.h" #include "kis_canvas_resource_provider.h" #include "kis_selection_manager.h" #include "kis_statusbar.h" #include "KisViewManager.h" #include "kis_zoom_manager.h" #include "canvas/kis_canvas2.h" #include "widgets/kis_meta_data_merge_strategy_chooser_widget.h" #include "widgets/kis_wdg_generator.h" #include "kis_progress_widget.h" #include "kis_node_commands_adapter.h" #include "kis_node_manager.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisPart.h" #include "kis_raster_keyframe_channel.h" #include "kis_signal_compressor_with_param.h" #include "kis_abstract_projection_plane.h" #include "commands_new/kis_set_layer_style_command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_selection_mask.h" #include "kis_layer_utils.h" #include "lazybrush/kis_colorize_mask.h" #include "KisSaveGroupVisitor.h" KisLayerManager::KisLayerManager(KisViewManager * view) : m_view(view) , m_imageView(0) , m_imageFlatten(0) , m_imageMergeLayer(0) , m_groupLayersSave(0) , m_imageResizeToLayer(0) , m_flattenLayer(0) , m_rasterizeLayer(0) , m_commandsAdapter(new KisNodeCommandsAdapter(m_view)) , m_layerStyle(0) { } KisLayerManager::~KisLayerManager() { delete m_commandsAdapter; } void KisLayerManager::setView(QPointerview) { m_imageView = view; } KisLayerSP KisLayerManager::activeLayer() { if (m_imageView) { return m_imageView->currentLayer(); } return 0; } KisPaintDeviceSP KisLayerManager::activeDevice() { if (activeLayer()) { return activeLayer()->paintDevice(); } return 0; } void KisLayerManager::activateLayer(KisLayerSP layer) { if (m_imageView) { emit sigLayerActivated(layer); layersUpdated(); if (layer) { m_view->resourceProvider()->slotNodeActivated(layer.data()); } } } void KisLayerManager::setup(KisActionManager* actionManager) { m_imageFlatten = actionManager->createAction("flatten_image"); connect(m_imageFlatten, SIGNAL(triggered()), this, SLOT(flattenImage())); m_imageMergeLayer = actionManager->createAction("merge_layer"); connect(m_imageMergeLayer, SIGNAL(triggered()), this, SLOT(mergeLayer())); m_flattenLayer = actionManager->createAction("flatten_layer"); connect(m_flattenLayer, SIGNAL(triggered()), this, SLOT(flattenLayer())); m_rasterizeLayer = actionManager->createAction("rasterize_layer"); connect(m_rasterizeLayer, SIGNAL(triggered()), this, SLOT(rasterizeLayer())); m_groupLayersSave = actionManager->createAction("save_groups_as_images"); connect(m_groupLayersSave, SIGNAL(triggered()), this, SLOT(saveGroupLayers())); m_convertGroupAnimated = actionManager->createAction("convert_group_to_animated"); connect(m_convertGroupAnimated, SIGNAL(triggered()), this, SLOT(convertGroupToAnimated())); m_imageResizeToLayer = actionManager->createAction("resizeimagetolayer"); connect(m_imageResizeToLayer, SIGNAL(triggered()), this, SLOT(imageResizeToActiveLayer())); KisAction *action = actionManager->createAction("trim_to_image"); connect(action, SIGNAL(triggered()), this, SLOT(trimToImage())); m_layerStyle = actionManager->createAction("layer_style"); connect(m_layerStyle, SIGNAL(triggered()), this, SLOT(layerStyle())); } void KisLayerManager::updateGUI() { KisImageWSP image = m_view->image(); KisLayerSP layer; qint32 nlayers = 0; if (image) { layer = activeLayer(); nlayers = image->nlayers(); } // XXX these should be named layer instead of image m_imageFlatten->setEnabled(nlayers > 1); m_imageMergeLayer->setEnabled(nlayers > 1 && layer && layer->prevSibling()); m_flattenLayer->setEnabled(nlayers > 1 && layer && layer->firstChild()); if (m_view->statusBar()) m_view->statusBar()->setProfile(image); } void KisLayerManager::imageResizeToActiveLayer() { KisLayerSP layer; KisImageWSP image = m_view->image(); if (image && (layer = activeLayer())) { QRect cropRect = layer->projection()->nonDefaultPixelArea(); if (!cropRect.isEmpty()) { image->cropImage(cropRect); } else { m_view->showFloatingMessage( i18nc("floating message in layer manager", "Layer is empty "), QIcon(), 2000, KisFloatingMessage::Low); } } } void KisLayerManager::trimToImage() { KisImageWSP image = m_view->image(); if (image) { image->cropImage(image->bounds()); } } void KisLayerManager::layerProperties() { if (!m_view) return; if (!m_view->document()) return; KisLayerSP layer = activeLayer(); QList selectedNodes = m_view->nodeManager()->selectedNodes(); const bool multipleLayersSelected = selectedNodes.size() > 1; if (!layer) return; KisAdjustmentLayerSP alayer = KisAdjustmentLayerSP(dynamic_cast(layer.data())); KisGeneratorLayerSP glayer = KisGeneratorLayerSP(dynamic_cast(layer.data())); if (alayer && !multipleLayersSelected) { KisPaintDeviceSP dev = alayer->projection(); KisDlgAdjLayerProps dlg(alayer, alayer.data(), dev, m_view, alayer->filter().data(), alayer->name(), i18n("Filter Layer Properties"), m_view->mainWindow(), "dlgadjlayerprops"); dlg.resize(dlg.minimumSizeHint()); KisFilterConfigurationSP configBefore(alayer->filter()); KIS_ASSERT_RECOVER_RETURN(configBefore); QString xmlBefore = configBefore->toXML(); if (dlg.exec() == QDialog::Accepted) { alayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(alayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, false); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } else { KisFilterConfigurationSP configAfter(dlg.filterConfiguration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { alayer->setFilter(KisFilterRegistry::instance()->cloneConfiguration(configBefore.data())); alayer->setDirty(); } } } else if (glayer && !multipleLayersSelected) { KisDlgGeneratorLayer dlg(glayer->name(), m_view, m_view->mainWindow()); dlg.setCaption(i18n("Fill Layer Properties")); KisFilterConfigurationSP configBefore(glayer->filter()); Q_ASSERT(configBefore); QString xmlBefore = configBefore->toXML(); dlg.setConfiguration(configBefore.data()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { glayer->setName(dlg.layerName()); KisFilterConfigurationSP configAfter(dlg.configuration()); Q_ASSERT(configAfter); QString xmlAfter = configAfter->toXML(); if(xmlBefore != xmlAfter) { KisChangeFilterCmd *cmd = new KisChangeFilterCmd(glayer, configBefore->name(), xmlBefore, configAfter->name(), xmlAfter, true); // FIXME: check whether is needed cmd->redo(); m_view->undoAdapter()->addCommand(cmd); m_view->document()->setModified(true); } } } else { // If layer == normal painting layer, vector layer, or group layer QList selectedNodes = m_view->nodeManager()->selectedNodes(); KisDlgLayerProperties *dialog = new KisDlgLayerProperties(selectedNodes, m_view); dialog->resize(dialog->minimumSizeHint()); dialog->setAttribute(Qt::WA_DeleteOnClose); Qt::WindowFlags flags = dialog->windowFlags(); dialog->setWindowFlags(flags | Qt::WindowStaysOnTopHint | Qt::Dialog); dialog->show(); } } void KisLayerManager::convertNodeToPaintLayer(KisNodeSP source) { KisImageWSP image = m_view->image(); if (!image) return; KisLayer *srcLayer = qobject_cast(source.data()); if (srcLayer && (srcLayer->inherits("KisGroupLayer") || srcLayer->layerStyle() || srcLayer->childCount() > 0)) { image->flattenLayer(srcLayer); return; } KisPaintDeviceSP srcDevice = source->paintDevice() ? source->projection() : source->original(); bool putBehind = false; QString newCompositeOp = source->compositeOpId(); KisColorizeMask *colorizeMask = dynamic_cast(source.data()); if (colorizeMask) { srcDevice = colorizeMask->coloringProjection(); putBehind = colorizeMask->compositeOpId() == COMPOSITE_BEHIND; if (putBehind) { newCompositeOp = COMPOSITE_OVER; } } if (!srcDevice) return; KisPaintDeviceSP clone; if (*srcDevice->colorSpace() != *srcDevice->compositionSourceColorSpace()) { clone = new KisPaintDevice(srcDevice->compositionSourceColorSpace()); QRect rc(srcDevice->extent()); KisPainter::copyAreaOptimized(rc.topLeft(), srcDevice, clone, rc); } else { clone = new KisPaintDevice(*srcDevice); } KisLayerSP layer = new KisPaintLayer(image, source->name(), source->opacity(), clone); layer->setCompositeOpId(newCompositeOp); KisNodeSP parent = source->parent(); KisNodeSP above = source; while (parent && !parent->allowAsChild(layer)) { above = above->parent(); parent = above ? above->parent() : 0; } if (putBehind && above == source->parent()) { above = above->prevSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to a Paint Layer")); m_commandsAdapter->addNode(layer, parent, above); m_commandsAdapter->removeNode(source); m_commandsAdapter->endMacro(); } void KisLayerManager::convertGroupToAnimated() { KisGroupLayerSP group = dynamic_cast(activeLayer().data()); if (group.isNull()) return; KisPaintLayerSP animatedLayer = new KisPaintLayer(m_view->image(), group->name(), OPACITY_OPAQUE_U8); animatedLayer->enableAnimation(); KisRasterKeyframeChannel *contentChannel = dynamic_cast( animatedLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); KIS_ASSERT_RECOVER_RETURN(contentChannel); KisNodeSP child = group->firstChild(); int time = 0; while (child) { contentChannel->importFrame(time, child->projection(), NULL); time++; child = child->nextSibling(); } m_commandsAdapter->beginMacro(kundo2_i18n("Convert to an animated layer")); m_commandsAdapter->addNode(animatedLayer, group->parent(), group); m_commandsAdapter->removeNode(group); m_commandsAdapter->endMacro(); } void KisLayerManager::adjustLayerPosition(KisNodeSP node, KisNodeSP activeNode, KisNodeSP &parent, KisNodeSP &above) { Q_ASSERT(activeNode); parent = activeNode; above = parent->lastChild(); while (parent && (!parent->allowAsChild(node) || parent->userLocked())) { above = parent; parent = parent->parent(); } if (!parent) { warnKrita << "KisLayerManager::adjustLayerPosition:" << "No node accepted newly created node"; parent = m_view->image()->root(); above = parent->lastChild(); } } void KisLayerManager::addLayerCommon(KisNodeSP activeNode, KisLayerSP layer, bool updateImage) { KisNodeSP parent; KisNodeSP above; adjustLayerPosition(layer, activeNode, parent, above); KisGroupLayer *group = dynamic_cast(parent.data()); const bool parentForceUpdate = group && !group->projectionIsValid(); updateImage |= parentForceUpdate; m_commandsAdapter->addNode(layer, parent, above, updateImage, updateImage); } KisLayerSP KisLayerManager::addLayer(KisNodeSP activeNode) { KisLayerSP layer = KisLayerUtils::constructDefaultLayer(m_view->image()); addLayerCommon(activeNode, layer, false); return layer; } void KisLayerManager::addGroupLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); addLayerCommon(activeNode, new KisGroupLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8), false); } void KisLayerManager::addCloneLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); addLayerCommon(activeNode, new KisCloneLayer(activeLayer(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8)); } void KisLayerManager::addShapeLayer(KisNodeSP activeNode) { if (!m_view) return; if (!m_view->document()) return; KisImageWSP image = m_view->image(); KisShapeLayerSP layer = new KisShapeLayer(m_view->document()->shapeController(), image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8); addLayerCommon(activeNode, layer, false); } void KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisSelectionSP selection = m_view->selection(); KisAdjustmentLayerSP adjl = addAdjustmentLayer(activeNode, QString(), 0, selection); image->refreshGraph(); KisPaintDeviceSP previewDevice = new KisPaintDevice(*adjl->original()); KisDlgAdjustmentLayer dlg(adjl, adjl.data(), previewDevice, image->nextLayerName(), i18n("New Filter Layer"), m_view); dlg.resize(dlg.minimumSizeHint()); // ensure that the device may be free'd by the dialog // when it is not needed anymore previewDevice = 0; if (dlg.exec() != QDialog::Accepted || adjl->filter().isNull()) { // XXX: add messagebox warning if there's no filter set! m_commandsAdapter->undoLastCommand(); } else { adjl->setName(dlg.layerName()); } } KisAdjustmentLayerSP KisLayerManager::addAdjustmentLayer(KisNodeSP activeNode, const QString & name, KisFilterConfigurationSP filter, KisSelectionSP selection) { KisImageWSP image = m_view->image(); KisAdjustmentLayerSP layer = new KisAdjustmentLayer(image, name, filter, selection); addLayerCommon(activeNode, layer); return layer; } void KisLayerManager::addGeneratorLayer(KisNodeSP activeNode) { KisImageWSP image = m_view->image(); KisDlgGeneratorLayer dlg(image->nextLayerName(), m_view, m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { KisSelectionSP selection = m_view->selection(); KisFilterConfigurationSP generator = dlg.configuration(); QString name = dlg.layerName(); addLayerCommon(activeNode, new KisGeneratorLayer(image, name, generator, selection)); } } void KisLayerManager::rotateLayer(double radians) { if (!m_view->image()) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(m_view->image())) return; + m_view->image()->rotateNode(layer, radians); } void KisLayerManager::shearLayer(double angleX, double angleY) { if (!m_view->image()) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(m_view->image())) return; + m_view->image()->shearNode(layer, angleX, angleY); } void KisLayerManager::flattenImage() { - KisImageWSP image = m_view->image(); + KisImageSP image = m_view->image(); + + if (!m_view->blockUntillOperationsFinished(image)) return; if (image) { bool doIt = true; if (image->nHiddenLayers() > 0) { int answer = QMessageBox::warning(m_view->mainWindow(), i18nc("@title:window", "Flatten Image"), i18n("The image contains hidden layers that will be lost. Do you want to flatten the image?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer != QMessageBox::Yes) { doIt = false; } } if (doIt) { image->flatten(); } } } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } bool tryMergeSelectionMasks(KisNodeSP currentNode, KisImageSP image) { bool result = false; KisNodeSP prevNode = currentNode->prevSibling(); if (isSelectionMask(currentNode) && prevNode && isSelectionMask(prevNode)) { QList mergedNodes; mergedNodes.append(currentNode); mergedNodes.append(prevNode); image->mergeMultipleLayers(mergedNodes, currentNode); result = true; } return result; } void KisLayerManager::mergeLayer() { - KisImageWSP image = m_view->image(); + KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(image)) return; + QList selectedNodes = m_view->nodeManager()->selectedNodes(); if (selectedNodes.size() > 1) { image->mergeMultipleLayers(selectedNodes, m_view->activeNode()); } else if (!tryMergeSelectionMasks(m_view->activeNode(), image)) { if (!layer->prevSibling()) return; KisLayer *prevLayer = qobject_cast(layer->prevSibling().data()); if (!prevLayer) return; if (layer->metaData()->isEmpty() && prevLayer->metaData()->isEmpty()) { image->mergeDown(layer, KisMetaData::MergeStrategyRegistry::instance()->get("Drop")); } else { const KisMetaData::MergeStrategy* strategy = KisMetaDataMergeStrategyChooserWidget::showDialog(m_view->mainWindow()); if (!strategy) return; image->mergeDown(layer, strategy); } } m_view->updateGUI(); } void KisLayerManager::flattenLayer() { - KisImageWSP image = m_view->image(); + KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(image)) return; + image->flattenLayer(layer); m_view->updateGUI(); } void KisLayerManager::rasterizeLayer() { - KisImageWSP image = m_view->image(); + KisImageSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(image)) return; + KisPaintLayerSP paintLayer = new KisPaintLayer(image, layer->name(), layer->opacity()); KisPainter gc(paintLayer->paintDevice()); QRect rc = layer->projection()->exactBounds(); gc.bitBlt(rc.topLeft(), layer->projection(), rc); m_commandsAdapter->beginMacro(kundo2_i18n("Rasterize Layer")); m_commandsAdapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); int childCount = layer->childCount(); for (int i = 0; i < childCount; i++) { m_commandsAdapter->moveNode(layer->firstChild(), paintLayer, paintLayer->lastChild()); } m_commandsAdapter->removeNode(layer); m_commandsAdapter->endMacro(); updateGUI(); } void KisLayerManager::layersUpdated() { KisLayerSP layer = activeLayer(); if (!layer) return; m_view->updateGUI(); } void KisLayerManager::saveGroupLayers() { QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); KoDialog dlg; QWidget *page = new QWidget(&dlg); dlg.setMainWidget(page); QBoxLayout *layout = new QVBoxLayout(page); KisFileNameRequester *urlRequester = new KisFileNameRequester(page); urlRequester->setMode(KoFileDialog::SaveFile); if (m_view->document()->url().isLocalFile()) { urlRequester->setStartDir(QFileInfo(m_view->document()->url().toLocalFile()).absolutePath()); } urlRequester->setMimeTypeFilters(listMimeFilter); urlRequester->setFileName(m_view->document()->url().toLocalFile()); layout->addWidget(urlRequester); QCheckBox *chkInvisible = new QCheckBox(i18n("Convert Invisible Groups"), page); chkInvisible->setChecked(false); layout->addWidget(chkInvisible); QCheckBox *chkDepth = new QCheckBox(i18n("Export Only Toplevel Groups"), page); chkDepth->setChecked(true); layout->addWidget(chkDepth); if (!dlg.exec()) return; QString path = urlRequester->fileName(); if (path.isEmpty()) return; QFileInfo f(path); QString mimeType= KisMimeDatabase::mimeTypeForFile(f.fileName()); if (mimeType.isEmpty()) { mimeType = "image/png"; } QString extension = KisMimeDatabase::suffixesForMimeType(mimeType).first(); QString basename = f.baseName(); - KisImageWSP image = m_view->image(); + KisImageSP image = m_view->image(); if (!image) return; KisSaveGroupVisitor v(image, chkInvisible->isChecked(), chkDepth->isChecked(), f.absolutePath(), basename, extension, mimeType); image->rootLayer()->accept(v); } bool KisLayerManager::activeLayerHasSelection() { return (activeLayer()->selection() != 0); } void KisLayerManager::addFileLayer(KisNodeSP activeNode) { QString basePath; QUrl url = m_view->document()->url(); if (url.isLocalFile()) { basePath = QFileInfo(url.toLocalFile()).absolutePath(); } KisImageWSP image = m_view->image(); KisDlgFileLayer dlg(basePath, image->nextLayerName(), m_view->mainWindow()); dlg.resize(dlg.minimumSizeHint()); if (dlg.exec() == QDialog::Accepted) { QString name = dlg.layerName(); QString fileName = dlg.fileName(); if(fileName.isEmpty()){ QMessageBox::critical(m_view->mainWindow(), i18nc("@title:window", "Krita"), i18n("No file name specified")); return; } KisFileLayer::ScalingMethod scalingMethod = dlg.scaleToImageResolution(); addLayerCommon(activeNode, new KisFileLayer(image, basePath, fileName, scalingMethod, name, OPACITY_OPAQUE_U8)); } } void updateLayerStyles(KisLayerSP layer, KisDlgLayerStyle *dlg) { KisSetLayerStyleCommand::updateLayerStyle(layer, dlg->style()->clone()); } void KisLayerManager::layerStyle() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = activeLayer(); if (!layer) return; + if (!m_view->blockUntillOperationsFinished(image)) return; + KisPSDLayerStyleSP oldStyle; if (layer->layerStyle()) { oldStyle = layer->layerStyle()->clone(); } else { oldStyle = toQShared(new KisPSDLayerStyle()); } KisDlgLayerStyle dlg(oldStyle->clone(), m_view->resourceProvider()); std::function updateCall(std::bind(updateLayerStyles, layer, &dlg)); SignalToFunctionProxy proxy(updateCall); connect(&dlg, SIGNAL(configChanged()), &proxy, SLOT(start())); if (dlg.exec() == QDialog::Accepted) { KisPSDLayerStyleSP newStyle = dlg.style(); KUndo2CommandSP command = toQShared( new KisSetLayerStyleCommand(layer, oldStyle, newStyle)); image->postExecutionUndoAdapter()->addCommand(command); } } diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp index e3d54d6e1c..125f0f32cb 100644 --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -1,1317 +1,1318 @@ /* * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include "canvas/kis_canvas2.h" #include "kis_shape_controller.h" #include "kis_canvas_resource_provider.h" #include "KisViewManager.h" #include "KisDocument.h" #include "kis_mask_manager.h" #include "kis_group_layer.h" #include "kis_layer_manager.h" #include "kis_selection_manager.h" #include "kis_node_commands_adapter.h" #include "kis_action.h" #include "kis_action_manager.h" #include "kis_processing_applicator.h" #include "kis_sequential_iterator.h" #include "kis_transaction.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_juggler_compressed.h" #include "kis_clipboard.h" #include "kis_node_dummies_graph.h" #include "kis_mimedata.h" #include "kis_layer_utils.h" #include "krita_utils.h" #include "processing/kis_mirror_processing_visitor.h" #include "KisView.h" struct KisNodeManager::Private { Private(KisNodeManager *_q, KisViewManager *v) : q(_q) , view(v) , imageView(0) , layerManager(v) , maskManager(v) , commandsAdapter(v) , nodeSelectionAdapter(new KisNodeSelectionAdapter(q)) , nodeInsertionAdapter(new KisNodeInsertionAdapter(q)) { } KisNodeManager * q; KisViewManager * view; QPointerimageView; KisLayerManager layerManager; KisMaskManager maskManager; KisNodeCommandsAdapter commandsAdapter; QScopedPointer nodeSelectionAdapter; QScopedPointer nodeInsertionAdapter; KisNodeList selectedNodes; QPointer nodeJuggler; bool activateNodeImpl(KisNodeSP node); QSignalMapper nodeCreationSignalMapper; QSignalMapper nodeConversionSignalMapper; void saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity); void mergeTransparencyMaskAsAlpha(bool writeToLayers); KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName); }; bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node) { Q_ASSERT(view); Q_ASSERT(view->canvasBase()); Q_ASSERT(view->canvasBase()->globalShapeManager()); Q_ASSERT(imageView); if (node && node == q->activeNode()) { return false; } // Set the selection on the shape manager to the active layer // and set call KoSelection::setActiveLayer( KoShapeLayer* layer ) // with the parent of the active layer. KoSelection *selection = view->canvasBase()->globalShapeManager()->selection(); Q_ASSERT(selection); selection->deselectAll(); if (!node) { selection->setActiveLayer(0); imageView->setCurrentNode(0); maskManager.activateMask(0); layerManager.activateLayer(0); } else { KoShape * shape = view->document()->shapeForNode(node); - Q_ASSERT(shape); + KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false); selection->select(shape); KoShapeLayer * shapeLayer = dynamic_cast(shape); - Q_ASSERT(shapeLayer); + KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false); + // shapeLayer->setGeometryProtected(node->userLocked()); // shapeLayer->setVisible(node->visible()); selection->setActiveLayer(shapeLayer); imageView->setCurrentNode(node); if (KisLayerSP layer = qobject_cast(node.data())) { maskManager.activateMask(0); layerManager.activateLayer(layer); } else if (KisMaskSP mask = dynamic_cast(node.data())) { maskManager.activateMask(mask); // XXX_NODE: for now, masks cannot be nested. layerManager.activateLayer(static_cast(node->parent().data())); } } return true; } KisNodeManager::KisNodeManager(KisViewManager *view) : m_d(new Private(this, view)) { connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP))); } KisNodeManager::~KisNodeManager() { delete m_d; } void KisNodeManager::setView(QPointerimageView) { m_d->maskManager.setView(imageView); m_d->layerManager.setView(imageView); if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this); m_d->imageView->image()->disconnect(this); } m_d->imageView = imageView; if (m_d->imageView) { KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP))); connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction())); connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&))); m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode()); } } #define NEW_LAYER_ACTION(id, layerType) \ { \ action = actionManager->createAction(id); \ m_d->nodeCreationSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeCreationSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION_2(id, layerType, exclude) \ { \ action = actionManager->createAction(id); \ action->setExcludedNodeTypes(QStringList(exclude)); \ actionManager->addAction(id, action); \ m_d->nodeConversionSignalMapper.setMapping(action, layerType); \ connect(action, SIGNAL(triggered()), \ &m_d->nodeConversionSignalMapper, SLOT(map())); \ } #define CONVERT_NODE_ACTION(id, layerType) \ CONVERT_NODE_ACTION_2(id, layerType, layerType) void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager) { m_d->layerManager.setup(actionManager); m_d->maskManager.setup(actionCollection, actionManager); KisAction * action = actionManager->createAction("mirrorNodeX"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX())); action = actionManager->createAction("mirrorNodeY"); connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY())); action = actionManager->createAction("activateNextLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode())); action = actionManager->createAction("activatePreviousLayer"); connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode())); action = actionManager->createAction("save_node_as_image"); connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage())); action = actionManager->createAction("duplicatelayer"); connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode())); action = actionManager->createAction("copy_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard())); action = actionManager->createAction("cut_layer_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard())); action = actionManager->createAction("paste_layer_from_clipboard"); connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard())); action = actionManager->createAction("create_quick_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup())); action = actionManager->createAction("create_quick_clipping_group"); connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup())); action = actionManager->createAction("quick_ungroup"); connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup())); action = actionManager->createAction("select_all_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes())); action = actionManager->createAction("select_visible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes())); action = actionManager->createAction("select_locked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes())); action = actionManager->createAction("select_invisible_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes())); action = actionManager->createAction("select_unlocked_layers"); connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes())); action = actionManager->createAction("new_from_visible"); connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible())); NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer"); NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer"); NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer"); NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer"); NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer"); NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer"); NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer"); NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask"); NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(createNode(const QString &))); CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer"); CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask"); CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask"); CONVERT_NODE_ACTION("convert_to_animated", "animated"); connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)), this, SLOT(convertNode(const QString &))); action = actionManager->createAction("isolate_layer"); connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool))); action = actionManager->createAction("split_alpha_into_mask"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask())); action = actionManager->createAction("split_alpha_write"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite())); // HINT: we can save even when the nodes are not editable action = actionManager->createAction("split_alpha_save_merged"); connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction())); connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode())); } void KisNodeManager::updateGUI() { // enable/disable all relevant actions m_d->layerManager.updateGUI(); m_d->maskManager.updateGUI(); } KisNodeSP KisNodeManager::activeNode() { if (m_d->imageView) { return m_d->imageView->currentNode(); } return 0; } KisLayerSP KisNodeManager::activeLayer() { return m_d->layerManager.activeLayer(); } const KoColorSpace* KisNodeManager::activeColorSpace() { if (m_d->maskManager.activeDevice()) { return m_d->maskManager.activeDevice()->colorSpace(); } else { Q_ASSERT(m_d->layerManager.activeLayer()); if (m_d->layerManager.activeLayer()->parentLayer()) return m_d->layerManager.activeLayer()->parentLayer()->colorSpace(); else return m_d->view->image()->colorSpace(); } } void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index) { if (parent->allowAsChild(node)) { if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) { KisSelectionMask *m = dynamic_cast(node.data()); KisLayer *l = qobject_cast(parent.data()); KisSelectionMaskSP selMask = l->selectionMask(); if (m && m->active() && l && l->selectionMask()) selMask->setActive(false); } m_d->commandsAdapter.moveNode(node, parent, index); } } void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Move Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, aboveThis); } void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Copy Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->copyNode(nodes, parent, aboveThis); } void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis) { KUndo2MagicString actionName = kundo2_i18n("Add Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->addNode(nodes, parent, aboveThis); } void KisNodeManager::toggleIsolateActiveNode() { KisImageWSP image = m_d->view->image(); KisNodeSP activeNode = this->activeNode(); KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode == image->isolatedModeRoot()) { toggleIsolateMode(false); } else { toggleIsolateMode(true); } } void KisNodeManager::toggleIsolateMode(bool checked) { KisImageWSP image = m_d->view->image(); if (checked) { KisNodeSP activeNode = this->activeNode(); // Transform and colorize masks don't have pixel data... if (activeNode->inherits("KisTransformMask") || activeNode->inherits("KisColorizeMask")) return; KIS_ASSERT_RECOVER_RETURN(activeNode); if (!image->startIsolatedMode(activeNode)) { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); action->setChecked(false); } } else { image->stopIsolatedMode(); } } void KisNodeManager::slotUpdateIsolateModeAction() { KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer"); Q_ASSERT(action); KisNodeSP activeNode = this->activeNode(); KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); action->setChecked(isolatedRootNode && isolatedRootNode == activeNode); } void KisNodeManager::slotTryFinishIsolatedMode() { KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot(); if (!isolatedRootNode) return; this->toggleIsolateMode(true); } void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } KIS_ASSERT_RECOVER_RETURN(activeNode); if (activeNode->systemLocked()) { return; } // XXX: make factories for this kind of stuff, // with a registry if (nodeType == "KisPaintLayer") { m_d->layerManager.addLayer(activeNode); } else if (nodeType == "KisGroupLayer") { m_d->layerManager.addGroupLayer(activeNode); } else if (nodeType == "KisAdjustmentLayer") { m_d->layerManager.addAdjustmentLayer(activeNode); } else if (nodeType == "KisGeneratorLayer") { m_d->layerManager.addGeneratorLayer(activeNode); } else if (nodeType == "KisShapeLayer") { m_d->layerManager.addShapeLayer(activeNode); } else if (nodeType == "KisCloneLayer") { m_d->layerManager.addCloneLayer(activeNode); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); } else if (nodeType == "KisColorizeMask") { m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, false); } else if (nodeType == "KisFileLayer") { m_d->layerManager.addFileLayer(activeNode); } } void KisNodeManager::createFromVisible() { KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild()); } KisLayerSP KisNodeManager::createPaintLayer() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) { activeNode = m_d->view->image()->root(); } return m_d->layerManager.addLayer(activeNode); } void KisNodeManager::convertNode(const QString &nodeType) { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; if (nodeType == "KisPaintLayer") { m_d->layerManager.convertNodeToPaintLayer(activeNode); } else if (nodeType == "KisSelectionMask" || nodeType == "KisFilterMask" || nodeType == "KisTransparencyMask") { KisPaintDeviceSP copyFrom = activeNode->paintDevice() ? activeNode->paintDevice() : activeNode->projection(); m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask")); if (nodeType == "KisSelectionMask") { m_d->maskManager.createSelectionMask(activeNode, copyFrom, true); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true); } else if (nodeType == "KisTransparencyMask") { m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true); } m_d->commandsAdapter.removeNode(activeNode); m_d->commandsAdapter.endMacro(); } else { warnKrita << "Unsupported node conversion type:" << nodeType; } } void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node) { KIS_ASSERT_RECOVER_RETURN(node != activeNode()); if (m_d->activateNodeImpl(node)) { emit sigUiNeedChangeActiveNode(node); emit sigNodeActivated(node); nodesUpdated(); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } } void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked(); if (toggled) { m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine); } } } void KisNodeManager::slotUiActivatedNode(KisNodeSP node) { if (node == activeNode()) return; slotSomethingActivatedNodeImpl(node); if (node) { QStringList vectorTools = QStringList() << "InteractionTool" << "KarbonPatternTool" << "KarbonGradientTool" << "KarbonCalligraphyTool" << "CreateShapesTool" << "PathTool"; QStringList pixelTools = QStringList() << "KritaShape/KisToolBrush" << "KritaShape/KisToolDyna" << "KritaShape/KisToolMultiBrush" << "KritaFill/KisToolFill" << "KritaFill/KisToolGradient"; if (node->inherits("KisShapeLayer")) { if (pixelTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("InteractionTool"); } } else { if (vectorTools.contains(KoToolManager::instance()->activeToolId())) { KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush"); } } } } void KisNodeManager::nodesUpdated() { KisNodeSP node = activeNode(); if (!node) return; m_d->layerManager.layersUpdated(); m_d->maskManager.masksUpdated(); m_d->view->updateGUI(); m_d->view->selectionManager()->selectionChanged(); } KisPaintDeviceSP KisNodeManager::activePaintDevice() { return m_d->maskManager.activeMask() ? m_d->maskManager.activeDevice() : m_d->layerManager.activeDevice(); } void KisNodeManager::nodeProperties(KisNodeSP node) { if (selectedNodes().size() > 1 || node->inherits("KisLayer")) { m_d->layerManager.layerProperties(); } else if (node->inherits("KisMask")) { m_d->maskManager.maskProperties(); } } qint32 KisNodeManager::convertOpacityToInt(qreal opacity) { /** * Scales opacity from the range 0...100 * to the integer range 0...255 */ return qMin(255, int(opacity * 2.55 + 0.5)); } void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity, bool finalChange) { if (!node) return; if (node->opacity() == opacity) return; if (!finalChange) { node->setOpacity(opacity); node->setDirty(); } else { m_d->commandsAdapter.setOpacity(node, opacity); } } void KisNodeManager::setNodeCompositeOp(KisNodeSP node, const KoCompositeOp* compositeOp) { if (!node) return; if (node->compositeOp() == compositeOp) return; m_d->commandsAdapter.setCompositeOp(node, compositeOp); } void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes) { if (activeNode) { slotNonUiActivatedNode(activeNode); } if (!selectedNodes.isEmpty()) { slotSetSelectedNodes(selectedNodes); } } void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes) { m_d->selectedNodes = nodes; emit sigUiNeedChangeSelectedNodes(nodes); } KisNodeList KisNodeManager::selectedNodes() { return m_d->selectedNodes; } KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const { return m_d->nodeSelectionAdapter.data(); } KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const { return m_d->nodeInsertionAdapter.data(); } void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange) { KisNodeSP node = activeNode(); setNodeOpacity(node, convertOpacityToInt(opacity), finalChange); } void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op) { KisNodeSP node = activeNode(); setNodeCompositeOp(node, op); } void KisNodeManager::duplicateActiveNode() { KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->duplicateNode(selectedNodes()); } KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName) { KisImageWSP image = view->image(); if (!nodeJuggler || (nodeJuggler && !nodeJuggler->canMergeAction(actionName))) { nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750); nodeJuggler->setAutoDelete(true); } return nodeJuggler; } void KisNodeManager::raiseNode() { KUndo2MagicString actionName = kundo2_i18n("Raise Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->raiseNode(selectedNodes()); } void KisNodeManager::lowerNode() { KUndo2MagicString actionName = kundo2_i18n("Lower Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->lowerNode(selectedNodes()); } void KisNodeManager::removeSingleNode(KisNodeSP node) { if (!node || !node->parent()) { return; } KisNodeList nodes; nodes << node; removeSelectedNodes(nodes); } void KisNodeManager::removeSelectedNodes(KisNodeList nodes) { KUndo2MagicString actionName = kundo2_i18n("Remove Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::removeNode() { removeSelectedNodes(selectedNodes()); } void KisNodeManager::mirrorNodeX() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer X"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask X"); } mirrorNode(node, commandName, Qt::Horizontal); } void KisNodeManager::mirrorNodeY() { KisNodeSP node = activeNode(); KUndo2MagicString commandName; if (node->inherits("KisLayer")) { commandName = kundo2_i18n("Mirror Layer Y"); } else if (node->inherits("KisMask")) { commandName = kundo2_i18n("Mirror Mask Y"); } mirrorNode(node, commandName, Qt::Vertical); } inline bool checkForGlobalSelection(KisNodeSP node) { return dynamic_cast(node.data()) && node->parent() && !node->parent()->parent(); } void KisNodeManager::activateNextNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node = activeNode->nextSibling(); while (node && node->childCount() > 0 && node->isEditable()) { node = node->firstChild(); } if (!node && activeNode->parent() && activeNode->parent()->parent()) { node = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->nextSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::activatePreviousNode() { KisNodeSP activeNode = this->activeNode(); if (!activeNode) return; KisNodeSP node; if (activeNode->childCount() > 0 && activeNode->isEditable()) { node = activeNode->lastChild(); } else { node = activeNode->prevSibling(); } while (!node && activeNode->parent()) { node = activeNode->parent()->prevSibling(); activeNode = activeNode->parent(); } while(node && checkForGlobalSelection(node)) { node = node->prevSibling(); } if (node) { slotNonUiActivatedNode(node); } } void KisNodeManager::mergeLayer() { m_d->layerManager.mergeLayer(); } void KisNodeManager::rotate(double radians) { // XXX: implement rotation for masks as well m_d->layerManager.rotateLayer(radians); } void KisNodeManager::rotate180() { rotate(M_PI); } void KisNodeManager::rotateLeft90() { rotate(-M_PI / 2); } void KisNodeManager::rotateRight90() { rotate(M_PI / 2); } void KisNodeManager::shear(double angleX, double angleY) { // XXX: implement shear for masks as well m_d->layerManager.shearLayer(angleX, angleY); } void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy) { KisNodeSP node = activeNode(); KIS_ASSERT_RECOVER_RETURN(node); m_d->view->image()->scaleNode(node, sx, sy, filterStrategy); nodesUpdated(); } void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation) { KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(m_d->view->image(), node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); nodesUpdated(); } void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device, const QString &defaultName, const QRect &bounds, qreal xRes, qreal yRes, quint8 opacity) { KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage"); dialog.setCaption(i18n("Export \"%1\"", defaultName)); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export)); QString filename = dialog.filename(); if (filename.isEmpty()) return; QUrl url = QUrl::fromLocalFile(filename); if (url.isEmpty()) return; QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);; QScopedPointer d(KisPart::instance()->createDocument()); KisImageSP dst = new KisImage(d->createUndoStore(), bounds.width(), bounds.height(), device->compositionSourceColorSpace(), defaultName); dst->setResolution(xRes, yRes); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity); paintLayer->paintDevice()->makeCloneFrom(device, bounds); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->initialRefreshGraph(); d->setOutputMimeType(mimefilter.toLatin1()); d->exportDocument(url); } void KisNodeManager::saveNodeAsImage() { KisNodeSP node = activeNode(); if (!node) { warnKrita << "BUG: Save Node As Image was called without any node selected"; return; } KisImageWSP image = m_d->view->image(); QRect saveRect = image->bounds() | node->exactBounds(); KisPaintDeviceSP device = node->paintDevice(); if (!device) { device = node->projection(); } m_d->saveDeviceAsImage(device, node->name(), saveRect, image->xRes(), image->yRes(), node->opacity()); } void KisNodeManager::slotSplitAlphaIntoMask() { KisNodeSP node = activeNode(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice()); KisPaintDeviceSP srcDevice = node->paintDevice(); const KoColorSpace *srcCS = srcDevice->colorSpace(); const QRect processRect = srcDevice->exactBounds() | srcDevice->defaultBounds()->bounds(); KisPaintDeviceSP selectionDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask")); KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice); KisSequentialIterator srcIt(srcDevice, processRect); KisSequentialIterator dstIt(selectionDevice, processRect); do { quint8 *srcPtr = srcIt.rawData(); quint8 *alpha8Ptr = dstIt.rawData(); *alpha8Ptr = srcCS->opacityU8(srcPtr); srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); m_d->commandsAdapter.addExtraCommand(transaction.endAndTake()); createNode("KisTransparencyMask", false, selectionDevice); m_d->commandsAdapter.endMacro(); } void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers) { KisNodeSP node = q->activeNode(); KisNodeSP parentNode = node->parent(); // guaranteed by KisActionManager KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask")); if (writeToLayers && !parentNode->hasEditablePaintDevice()) { QMessageBox::information(view->mainWindow(), i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()), i18n("Cannot write alpha channel of " "the parent layer \"%1\".\n" "The operation will be cancelled.").arg(parentNode->name())); return; } KisPaintDeviceSP dstDevice; if (writeToLayers) { KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice()); dstDevice = parentNode->paintDevice(); } else { KisPaintDeviceSP copyDevice = parentNode->paintDevice(); if (!copyDevice) { copyDevice = parentNode->original(); } dstDevice = new KisPaintDevice(*copyDevice); } const KoColorSpace *dstCS = dstDevice->colorSpace(); KisPaintDeviceSP selectionDevice = node->paintDevice(); KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1); const QRect processRect = selectionDevice->exactBounds() | dstDevice->exactBounds() | selectionDevice->defaultBounds()->bounds(); QScopedPointer transaction; if (writeToLayers) { commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer")); transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice)); } KisSequentialIterator srcIt(selectionDevice, processRect); KisSequentialIterator dstIt(dstDevice, processRect); do { quint8 *alpha8Ptr = srcIt.rawData(); quint8 *dstPtr = dstIt.rawData(); dstCS->setOpacity(dstPtr, *alpha8Ptr, 1); } while (srcIt.nextPixel() && dstIt.nextPixel()); if (writeToLayers) { commandsAdapter.addExtraCommand(transaction->endAndTake()); commandsAdapter.removeNode(node); commandsAdapter.endMacro(); } else { KisImageWSP image = view->image(); QRect saveRect = image->bounds(); saveDeviceAsImage(dstDevice, parentNode->name(), saveRect, image->xRes(), image->yRes(), OPACITY_OPAQUE_U8); } } void KisNodeManager::slotSplitAlphaWrite() { m_d->mergeTransparencyMaskAsAlpha(true); } void KisNodeManager::slotSplitAlphaSaveMerged() { m_d->mergeTransparencyMaskAsAlpha(false); } void KisNodeManager::cutLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisNodeSP root = m_d->view->image()->root(); if (nodes.isEmpty()) return; KisClipboard::instance()->setLayers(nodes, root, false); KUndo2MagicString actionName = kundo2_i18n("Cut Nodes"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->removeNode(nodes); } void KisNodeManager::copyLayersToClipboard() { KisNodeList nodes = this->selectedNodes(); KisNodeSP root = m_d->view->image()->root(); KisClipboard::instance()->setLayers(nodes, root, true); } void KisNodeManager::pasteLayersFromClipboard() { const QMimeData *data = KisClipboard::instance()->layersMimeData(); if (!data) return; KisNodeSP activeNode = this->activeNode(); KisShapeController *shapeController = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(shapeController); KisDummiesFacadeBase *dummiesFacade = dynamic_cast(m_d->imageView->document()->shapeController()); Q_ASSERT(dummiesFacade); const bool copyNode = false; KisImageSP image = m_d->view->image(); KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode); KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0; KisMimeData::insertMimeLayers(data, image, shapeController, parentDummy, aboveThisDummy, copyNode, nodeInsertionAdapter()); } void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler, const QString &overrideGroupName, KisNodeSP *newGroup, KisNodeSP *newLastChild) { KisNodeSP active = activeNode(); if (!active) return; KisImageSP image = m_d->view->image(); QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName(); KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8); KisNodeList nodes1; nodes1 << group; KisNodeList nodes2; nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes()); KisLayerUtils::filterMergableNodes(nodes2); if (KisLayerUtils::checkIsChildOf(active, nodes2)) { active = nodes2.first(); } KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; juggler->addNode(nodes1, parent, aboveThis); juggler->moveNode(nodes2, group, 0); *newGroup = group; *newLastChild = nodes2.last(); } void KisNodeManager::createQuickGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; createQuickGroupImpl(juggler, "", &parent, &above); } void KisNodeManager::createQuickClippingGroup() { KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group"); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); KisNodeSP parent; KisNodeSP above; KisImageSP image = m_d->view->image(); createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above); KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace()); maskLayer->disableAlphaChannel(true); juggler->addNode(KisNodeList() << maskLayer, parent, above); } void KisNodeManager::quickUngroup() { KisNodeSP active = activeNode(); if (!active) return; KisNodeSP parent = active->parent(); KisNodeSP aboveThis = active; KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup"); if (parent && dynamic_cast(active.data())) { KisNodeList nodes = active->childNodes(QStringList(), KoProperties()); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(nodes, parent, active); juggler->removeNode(KisNodeList() << active); } else if (parent && parent->parent()) { KisNodeSP grandParent = parent->parent(); KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties()); KisNodeList allSelectedNodes = selectedNodes(); const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes); KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName); juggler->moveNode(allSelectedNodes, grandParent, parent); if (removeParent) { juggler->removeNode(KisNodeList() << parent); } } } void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps) { KisImageSP image = m_d->view->image(); KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true); KisNodeList selectedNodes = this->selectedNodes(); if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) { nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true); } if (!nodes.isEmpty()) { slotImageRequestNodeReselection(nodes.last(), nodes); } } void KisNodeManager::selectAllNodes() { KoProperties props; selectLayersImpl(props, props); } void KisNodeManager::selectVisibleNodes() { KoProperties props; props.setProperty("visible", true); KoProperties invertedProps; invertedProps.setProperty("visible", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectLockedNodes() { KoProperties props; props.setProperty("locked", true); KoProperties invertedProps; invertedProps.setProperty("locked", false); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectInvisibleNodes() { KoProperties props; props.setProperty("visible", false); KoProperties invertedProps; invertedProps.setProperty("visible", true); selectLayersImpl(props, invertedProps); } void KisNodeManager::selectUnlockedNodes() { KoProperties props; props.setProperty("locked", false); KoProperties invertedProps; invertedProps.setProperty("locked", true); selectLayersImpl(props, invertedProps); } diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 9b948d9a51..228235dcfd 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1282 +1,1282 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); KisAction* hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); hideCanvasDecorationsX->setCheckable(true); hideCanvasDecorationsX->setText(i18n("Hide Mirror Line")); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); KisAction* lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); lockActionX->setText(i18n("Lock")); lockActionX->setCheckable(true); toolbarMenuXMirror->addAction(lockActionX); KisAction* moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); moveToCenterActionX->setCheckable(false); moveToCenterActionX->setText(i18n("Move to Canvas Center")); toolbarMenuXMirror->addAction(moveToCenterActionX); // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); KisAction* hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); hideCanvasDecorationsY->setCheckable(true); hideCanvasDecorationsY->setText(i18n("Hide Mirror Line")); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); KisAction* lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); lockActionY->setText(i18n("Lock")); lockActionY->setCheckable(true); toolbarMenuYMirror->addAction(lockActionY); KisAction* moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); moveToCenterActionY->setCheckable(false); moveToCenterActionY->setText(i18n("Move to Canvas Center")); toolbarMenuYMirror->addAction(moveToCenterActionY); // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0.0, 1.0, 2); slOpacity->setValue(1.0); slOpacity->setSingleStep(0.05); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0.0, 1.0, 2); slFlow->setValue(1.0); slFlow->setSingleStep(0.05); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0, 1000, 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); QWidget* compositePressure = new QWidget(this); QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure); pressureLayout->addWidget(m_disablePressureButton); pressureLayout->setSpacing(4); pressureLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); view->actionCollection()->addAction("pressure_action", action); action->setText(i18n("Pressure usage (small button)")); action->setDefaultWidget(compositePressure); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); - m_presetsPopup->switchDetached(false); + m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); + connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); - m_presetsChooserPopup->setFixedSize(500, 600); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(savePresetClicked()) , SLOT(slotSaveActivePreset())); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); m_favoriteResourceManager = new KisFavoriteResourceManager(this); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg; QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } } // Do not delete the widget, since it it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { if (preset == m_resourceProvider->currentPreset()) { return; } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = m_resourceProvider->currentPreset(); m_tabletToolMap[m_currTabletToolID].preset = m_resourceProvider->currentPreset(); m_tabletToolMap[m_currTabletToolID].paintOpID = m_resourceProvider->currentPreset()->paintOp(); } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id()); QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap()); m_brushEditorPopupButton->setIcon(QIcon(pixFilename)); m_presetsPopup->setCurrentPaintOp(paintop.id()); if (m_presetsPopup->currentPaintOp() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); slider->setValue(value); } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg; KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle")); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default")); } if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (preset) { setCurrentPaintop(preset); } } else { if (toolData->preset) { setCurrentPaintop(toolData->preset); } else { setCurrentPaintop(toolData->paintOpID); } } m_currTabletToolID = TabletToolID(inputDevice); } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotSaveActivePreset() { KisPaintOpPresetSP curPreset = m_resourceProvider->currentPreset(); if (!curPreset) return; m_favoriteResourceManager->setBlockUpdates(true); KisPaintOpPresetSP newPreset = curPreset->clone(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QString saveLocation = rServer->saveLocation(); QString presetName = m_presetsPopup->getPresetName(); QString presetFilename = saveLocation + presetName + newPreset->defaultFileExtension(); QStringList tags; KisPaintOpPresetSP resource = rServer->resourceByName(presetName); if (resource) { tags = rServer->assignedTagsList(resource.data()); rServer->removeResourceAndBlacklist(resource); } newPreset->setImage(m_presetsPopup->cutOutOverlay()); newPreset->setFilename(presetFilename); newPreset->setName(presetName); newPreset->setPresetDirty(false); rServer->addResource(newPreset); Q_FOREACH (const QString & tag, tags) { rServer->addTag(newPreset.data(), tag); } // HACK ALERT! the server does not notify the observers // automatically, so we need to call theupdate manually! rServer->tagCategoryMembersChanged(); restoreResource(newPreset.data()); m_favoriteResourceManager->setBlockUpdates(false); } void KisPaintopBox::slotUpdatePreset() { if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value(); qreal flow = m_sliderChooser[n]->getWidget("flow")->value(); qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS | ENABLE_SIZE | ENABLE_FLOW); slotUpdatePreset(); m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS | DISABLE_SIZE | DISABLE_FLOW); m_presetsEnabled = false; } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == preset->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } return; } i++; } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset()->name() == preset->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } return; } i++; } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { setCurrentPaintop(m_resourceProvider->previousPreset()); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until thye entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } //slotUpdatePreset(); } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg; cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg; cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg; cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg; if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/kis_paintop_settings_widget.cpp b/libs/ui/kis_paintop_settings_widget.cpp index ed5b65a7be..756fb31d7b 100644 --- a/libs/ui/kis_paintop_settings_widget.cpp +++ b/libs/ui/kis_paintop_settings_widget.cpp @@ -1,230 +1,232 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * Copyright (C) Silvio Heinrich , (C) 2011 * * 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_paintop_settings_widget.h" #include "kis_paintop_option.h" #include "kis_paintop_options_model.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KisPaintOpSettingsWidget::Private { QList paintOpOptions; KisCategorizedListView* optionsList; KisPaintOpOptionListModel* model; QStackedWidget* optionsStack; }; KisPaintOpSettingsWidget::KisPaintOpSettingsWidget(QWidget * parent) : KisPaintOpConfigWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsWidget"); m_d->model = new KisPaintOpOptionListModel(this); m_d->optionsList = new KisCategorizedListView(false, this); m_d->optionsList->setModel(m_d->model); m_d->optionsList->setItemDelegate(new KisCategorizedItemDelegate(m_d->optionsList)); m_d->optionsList->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); m_d->optionsList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QSizePolicy policy = QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_d->optionsList->setSizePolicy(policy); + m_d->optionsList->setMinimumWidth(130); // this should be just big enough to show all of the setting names + m_d->optionsStack = new QStackedWidget(this); policy = QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_d->optionsStack->setSizePolicy(policy); QHBoxLayout* layout = new QHBoxLayout(this); layout->addWidget(m_d->optionsList); layout->addWidget(m_d->optionsStack); layout->setStretch(0, 0); layout->setStretch(1, 1); m_saveLockedOption = false; connect(m_d->optionsList, SIGNAL(activated(const QModelIndex&)), this, SLOT(changePage(const QModelIndex&))); connect(m_d->optionsList, SIGNAL(clicked(QModelIndex)), this, SLOT(changePage(const QModelIndex&))); connect(m_d->optionsList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(lockProperties(const QModelIndex&))); connect(m_d->optionsList, SIGNAL(rightClickedMenuDropSettingsTriggered()), this, SLOT(slotLockPropertiesDrop())); connect(m_d->optionsList, SIGNAL(rightClickedMenuSaveSettingsTriggered()), this, SLOT(slotLockPropertiesSave())); connect(m_d->optionsList, SIGNAL(sigEntryChecked(QModelIndex)), this, SLOT(slotEntryChecked(QModelIndex))); } KisPaintOpSettingsWidget::~KisPaintOpSettingsWidget() { qDeleteAll(m_d->paintOpOptions); delete m_d; } void KisPaintOpSettingsWidget::addPaintOpOption(KisPaintOpOption *option, const QString &label) { if (!option->configurationPage()) return; m_d->model->addPaintOpOption(option, m_d->optionsStack->count(), label); connect(option, SIGNAL(sigSettingChanged()), SIGNAL(sigConfigurationItemChanged())); m_d->optionsStack->addWidget(option->configurationPage()); m_d->paintOpOptions << option; } void KisPaintOpSettingsWidget::setConfiguration(const KisPropertiesConfigurationSP config) { Q_ASSERT(!config->getString("paintop").isEmpty()); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(config); int indexcount = 0; Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) { option->startReadOptionSetting(propertiesProxy); if (KisLockedPropertiesServer::instance()->propertiesFromLocked()) { option->setLocked(true); } else { option->setLocked(false); } KisLockedPropertiesServer::instance()->setPropertiesFromLocked(false); KisOptionInfo info; info.option = option; info.index = indexcount; m_d->model->categoriesMapper()->itemFromRow(m_d->model->indexOf(info).row())->setLocked(option->isLocked()); m_d->model->categoriesMapper()->itemFromRow(m_d->model->indexOf(info).row())->setLockable(true); m_d->model->signalDataChanged(m_d->model->indexOf(info)); indexcount++; } } void KisPaintOpSettingsWidget::writeConfiguration(KisPropertiesConfigurationSP config) const { KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(config); Q_FOREACH (const KisPaintOpOption* option, m_d->paintOpOptions) { option->startWriteOptionSetting(propertiesProxy); } } KisPaintopLodLimitations KisPaintOpSettingsWidget::lodLimitations() const { KisPaintopLodLimitations l; Q_FOREACH (const KisPaintOpOption* option, m_d->paintOpOptions) { if (option->isCheckable() && !option->isChecked()) continue; option->lodLimitations(&l); } return l; } void KisPaintOpSettingsWidget::setImage(KisImageWSP image) { Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) { option->setImage(image); } } void KisPaintOpSettingsWidget::setNode(KisNodeWSP node) { Q_FOREACH (KisPaintOpOption* option, m_d->paintOpOptions) { option->setNode(node); } } void KisPaintOpSettingsWidget::changePage(const QModelIndex& index) { KisOptionInfo info; QPalette palette; palette.setColor(QPalette::Base, QColor(255,200,200)); palette.setColor(QPalette::Text, Qt::black); if(m_d->model->entryAt(info, index)) { m_d->optionsStack->setCurrentIndex(info.index); } notifyPageChanged(); } void KisPaintOpSettingsWidget::notifyPageChanged() { } void KisPaintOpSettingsWidget::lockProperties(const QModelIndex& index) { KisOptionInfo info; if (m_d->model->entryAt(info, index)) { m_d->optionsList->setCurrentIndex(index); KisPropertiesConfigurationSP p = new KisPropertiesConfiguration(); info.option->startWriteOptionSetting(p); if (!info.option->isLocked()){ KisLockedPropertiesServer::instance()->addToLockedProperties(p); info.option->setLocked(true); m_d->model->categoriesMapper()->itemFromRow(index.row())->setLocked(true); } else { KisLockedPropertiesServer::instance()->removeFromLockedProperties(p); info.option->setLocked(false); m_d->model->categoriesMapper()->itemFromRow(index.row())->setLocked(false); if (m_saveLockedOption){ emit sigSaveLockedConfig(p); } else { emit sigDropLockedConfig(p); } m_saveLockedOption = false; } m_d->model->signalDataChanged(index); } } void KisPaintOpSettingsWidget::slotLockPropertiesDrop() { m_saveLockedOption = false; lockProperties(m_d->optionsList->currentIndex()); } void KisPaintOpSettingsWidget::slotLockPropertiesSave() { m_saveLockedOption = true; lockProperties(m_d->optionsList->currentIndex()); } void KisPaintOpSettingsWidget::slotEntryChecked(const QModelIndex &index) { Q_UNUSED(index); emit sigConfigurationItemChanged(); } diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp index 4d92108155..4a4975281b 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1268 +1,1276 @@ /* * Copyright (c) 2005-2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #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 "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } ~KisPNGReaderLineByLine() override { delete[] row_pointer; } png_bytep readLine() override { png_read_row(png_ptr, row_pointer, 0); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } ~KisPNGReaderFullImage() override { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } png_bytep readLine() override { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); return (KisImageBuilder_RESULT_BAD_FETCH); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; // Get the various optional chunks // https://www.w3.org/TR/PNG/#11cHRM #if defined(PNG_cHRM_SUPPORTED) double whitePointX, whitePointY; double redX, redY; double greenX, greenY; double blueX, blueY; png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY); qDebug() << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY; #endif // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) double gamma; png_get_gAMA(png_ptr, info_ptr, &gamma); qDebug() << "gAMA" << gamma; #endif // https://www.w3.org/TR/PNG/#11sRGB #if defined(PNG_sRGB_SUPPORTED) int sRGBIntent; png_get_sRGB(png_ptr, info_ptr, &sRGBIntent); qDebug() << "sRGB" << sRGBIntent; #endif bool fromBlender = false; png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); if (key == "file") { QString relatedFile = text_ptr[i].text; if (relatedFile.contains(".blend", Qt::CaseInsensitive)){ fromBlender=true; } } } const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg; quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); QApplication::restoreOverrideCursor(); dlg.exec(); if (!dlg.profile().isEmpty()) { QString s = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s); if (csf) { QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf); Q_FOREACH (const KoColorProfile *p, profileList) { if (p->name() == dlg.profile()) { profile = p; break; } } } } QApplication::setOverrideCursor(Qt::WaitCursor); } } dbgFile << "no embedded profile, will use the default profile"; } // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory( KoColorSpaceRegistry::instance()->colorSpaceId( csName.first, csName.second))->profileIsCompatible(profile)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Creating the KisImageWSP if (m_image == 0) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); Q_CHECK_PTR(m_image); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); dbgFile << "key is |" << text_ptr[i].key << "| containing " << text_ptr[i].text << " " << (key == "Raw profile type exif "); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "description") { info->setAboutInfo("comment", text_ptr[i].text); } else if (key == "author") { qDebug()<<"Author:"<setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("Raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("Raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("Raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data KisPNGReaderAbstract* reader = 0; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader = new KisPNGReaderFullImage(png_ptr, info_ptr, width, height); } else { reader = new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height); } } catch (std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return (KisImageBuilder_RESULT_FAILURE); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (transform) transform->transform(d, d, 1); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: return KisImageBuilder_RESULT_UNSUPPORTED; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); delete reader; return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename) { m_path = filename; QFile fp(filename); if (fp.exists()) { if (!fp.open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; return (KisImageBuilder_RESULT_FAILURE); } return buildImage(&fp); } return (KisImageBuilder_RESULT_NOT_EXIST); } KisImageSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; + options.saveSRGBProfile = false; + + if (dev->colorSpace()->id() != "RGBA") { + dev = new KisPaintDevice(*dev.data()); + KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); + delete cmd; + } + bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); if (success != KisImageBuilder_RESULT_OK) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << filename; // Open a QIODevice for writing QFile fp (filename); if (!fp.open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; return (KisImageBuilder_RESULT_FAILURE); } KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); return result; } KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { if (!device) return KisImageBuilder_RESULT_INVALID_ARG; if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (options.forceSRGB) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); device->convertTo(cs); } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { return (KisImageBuilder_RESULT_FAILURE); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); return (KisImageBuilder_RESULT_FAILURE); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return (KisImageBuilder_RESULT_FAILURE); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f png_colorp palette = 0; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette = new png_color[255]; KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; do { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } while (it.nextPixel()); if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { delete [] palette; } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); /* * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's * color management is bugged, so once you give it any incentive to start color managing an sRGB image it * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now. */ /*if (!options.saveSRGBProfile && sRGB) { png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); }*/ // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette, num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (const png_bytep)colorProfileData.constData(), colorProfileData . size()); #else png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (char*)colorProfileData.constData(), colorProfileData . size()); #endif } // read comments from the document information // warning: according to the official png spec, the keys need to be capitalised! if (m_doc) { png_text texts[3]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("comment"); if (!abstract.isEmpty()) { fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { fillText(texts + nbtexts, "Author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0); // Fill the data structure png_byte** row_pointers = new png_byte*[imageRect.height()]; int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); row_pointers[row] = new png_byte[imageRect.width() * device->pixelSize()]; switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(row_pointers[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = row_pointers[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = row_pointers[row]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: delete[] row_pointers; return KisImageBuilder_RESULT_UNSUPPORTED; } } png_write_image(png_ptr, row_pointers); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); for (int y = 0; y < imageRect.height(); y++) { delete[] row_pointers[y]; } delete[] row_pointers; if (color_type == PNG_COLOR_TYPE_PALETTE) { delete [] palette; } iodevice->close(); return KisImageBuilder_RESULT_OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/libs/ui/kis_png_converter.h b/libs/ui/kis_png_converter.h index b13d89a00d..0bc43121f0 100644 --- a/libs/ui/kis_png_converter.h +++ b/libs/ui/kis_png_converter.h @@ -1,134 +1,138 @@ /* * Copyright (c) 2005, 2007 Cyrille Berger * * 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_PNG_CONVERTER_H_ #define _KIS_PNG_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" #include #include class KoStore; class KisDocument; class KoColorSpace; namespace KisMetaData { class Filter; class Store; } struct KisPNGOptions { KisPNGOptions() : compression(0) , interlace(false) , alpha(true) , exif(true) , iptc(true) , xmp(true) , tryToSaveAsIndexed(true) , saveSRGBProfile(false) , forceSRGB(false) , transparencyFillColor(Qt::white) {} int compression; bool interlace; bool alpha; bool exif; bool iptc; bool xmp; bool tryToSaveAsIndexed; bool saveSRGBProfile; bool forceSRGB; QList filters; QColor transparencyFillColor; }; /** * This class allows to import/export a PNG from either a file or a QIODevice. */ // XXX_PROGRESS (pass KoUpdater to the png converter) class KRITAUI_EXPORT KisPNGConverter : public QObject { Q_OBJECT public: /** * Initialize the converter. * @param doc the KisDocument related to the image, can be null if you don't have a KisDocument * @param adapter the undo adapter to be used by the image, can be null if you don't want to use an undo adapter */ KisPNGConverter(KisDocument *doc, bool batchMode = false); virtual ~KisPNGConverter(); public: /** * Load an image from an URL. If the image is not on a local drive, the image is first downloaded to a * temporary location. * @param uri the url of the image */ KisImageBuilder_Result buildImage(const QString &filename); /** * Load an image from a QIODevice. * @param iod device to access the data */ KisImageBuilder_Result buildImage(QIODevice* iod); /** * Save a layer to a PNG * @param uri the url of the destination file * @param device the paint device to save * @param annotationsStart an iterator on the first annotation * @param annotationsEnd an iterator on the last annotation * @param compression a number between 0 and 9 to specify the compression rate (9 is most compressed) * @param interlace set to true if you want to generate an interlaced png * @param alpha set to true if you want to save the alpha channel */ KisImageBuilder_Result buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); KisImageBuilder_Result buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); /** * Retrieve the constructed image */ KisImageSP image(); + /** + * @brief saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB. + * @return true if the saving succeeds + */ static bool saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData = 0); static bool isColorSpaceSupported(const KoColorSpace *cs); public Q_SLOTS: virtual void cancel(); private: void progress(png_structp png_ptr, png_uint_32 row_number, int pass); private: png_uint_32 m_max_row; KisImageSP m_image; KisDocument *m_doc; bool m_stop; bool m_batchMode; QString m_path; }; #endif diff --git a/libs/ui/kis_popup_palette.cpp b/libs/ui/kis_popup_palette.cpp index 887c81a6ba..79804fd8f3 100644 --- a/libs/ui/kis_popup_palette.cpp +++ b/libs/ui/kis_popup_palette.cpp @@ -1,858 +1,903 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2011 Sven Langkamp Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kis_popup_palette.h" #include "kis_paintop_box.h" #include "kis_favorite_resource_manager.h" #include "kis_icon_utils.h" #include #include "kis_resource_server_provider.h" #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_signal_compressor.h" #include #include "brushhud/kis_brush_hud.h" #include "brushhud/kis_round_hud_button.h" #include +class PopupColorTriangle : public KoTriangleColorSelector +{ +public: + PopupColorTriangle(const KoColorDisplayRendererInterface *displayRenderer, QWidget* parent) + : KoTriangleColorSelector(displayRenderer, parent) + , m_dragging(false) { + } + ~PopupColorTriangle() override {} + + void tabletEvent(QTabletEvent* event) override { + event->accept(); + QMouseEvent* mouseEvent = 0; + switch (event->type()) { + case QEvent::TabletPress: + mouseEvent = new QMouseEvent(QEvent::MouseButtonPress, event->pos(), + Qt::LeftButton, Qt::LeftButton, event->modifiers()); + m_dragging = true; + mousePressEvent(mouseEvent); + break; + case QEvent::TabletMove: + mouseEvent = new QMouseEvent(QEvent::MouseMove, event->pos(), + (m_dragging) ? Qt::LeftButton : Qt::NoButton, + (m_dragging) ? Qt::LeftButton : Qt::NoButton, event->modifiers()); + mouseMoveEvent(mouseEvent); + break; + case QEvent::TabletRelease: + mouseEvent = new QMouseEvent(QEvent::MouseButtonRelease, event->pos(), + Qt::LeftButton, + Qt::LeftButton, + event->modifiers()); + m_dragging = false; + mouseReleaseEvent(mouseEvent); + break; + default: break; + } + delete mouseEvent; + } + +private: + bool m_dragging; +}; KisPopupPalette::KisPopupPalette(KisViewManager* viewManager, KisCoordinatesConverter* coordinatesConverter ,KisFavoriteResourceManager* manager, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent) : QWidget(parent, Qt::FramelessWindowHint) , m_hoveredPreset(0) , m_hoveredColor(0) , m_selectedColor(0) , m_coordinatesConverter(coordinatesConverter) , m_actionManager(viewManager->actionManager()) , m_resourceManager(manager) , m_triangleColorSelector(0) , m_displayRenderer(displayRenderer) , m_colorChangeCompressor(new KisSignalCompressor(50, KisSignalCompressor::POSTPONE)) , m_actionCollection(viewManager->actionCollection()) , m_brushHud(0) , m_popupPaletteSize(385.0) , m_colorHistoryInnerRadius(72.0) , m_colorHistoryOuterRadius(92.0) , m_isOverCanvasRotationIndicator(false) , m_isRotatingCanvasIndicator(false) { // some UI controls are defined and created based off these variables const int borderWidth = 3; - m_triangleColorSelector = new KisVisualColorSelector(this); + if (KisConfig().readEntry("popuppalette/usevisualcolorselector", false)) { + m_triangleColorSelector = new KisVisualColorSelector(this); + } + else { + m_triangleColorSelector = new PopupColorTriangle(displayRenderer, this); + } m_triangleColorSelector->setDisplayRenderer(displayRenderer); m_triangleColorSelector->setConfig(true,false); m_triangleColorSelector->move(m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth, m_popupPaletteSize/2-m_colorHistoryInnerRadius+borderWidth); m_triangleColorSelector->resize(m_colorHistoryInnerRadius*2-borderWidth*2, m_colorHistoryInnerRadius*2-borderWidth*2); m_triangleColorSelector->setVisible(true); KoColor fgcolor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()); if (m_resourceManager) { fgcolor = provider->fgColor(); } m_triangleColorSelector->slotSetColor(fgcolor); QRegion maskedRegion(0, 0, m_triangleColorSelector->width(), m_triangleColorSelector->height(), QRegion::Ellipse ); m_triangleColorSelector->setMask(maskedRegion); //setAttribute(Qt::WA_TranslucentBackground, true); - connect(m_triangleColorSelector, SIGNAL(sigNewColor(KoColor)), + connect(m_triangleColorSelector, SIGNAL(sigNewColor(const KoColor &)), m_colorChangeCompressor.data(), SLOT(start())); connect(m_colorChangeCompressor.data(), SIGNAL(timeout()), SLOT(slotEmitColorChanged())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_triangleColorSelector, SLOT(configurationChanged())); connect(m_resourceManager, SIGNAL(sigChangeFGColorSelector(KoColor)), SLOT(slotExternalFgColorChanged(KoColor))); connect(this, SIGNAL(sigChangefGColor(KoColor)), m_resourceManager, SIGNAL(sigSetFGColor(KoColor))); connect(this, SIGNAL(sigChangeActivePaintop(int)), m_resourceManager, SLOT(slotChangeActivePaintop(int))); connect(this, SIGNAL(sigUpdateRecentColor(int)), m_resourceManager, SLOT(slotUpdateRecentColor(int))); connect(m_resourceManager, SIGNAL(setSelectedColor(int)), SLOT(slotSetSelectedColor(int))); connect(m_resourceManager, SIGNAL(updatePalettes()), SLOT(slotUpdate())); connect(m_resourceManager, SIGNAL(hidePalettes()), SLOT(slotHide())); // This is used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible m_timer.setSingleShot(true); connect(this, SIGNAL(sigTriggerTimer()), this, SLOT(slotTriggerTimer())); connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotEnableChangeFGColor())); connect(this, SIGNAL(sigEnableChangeFGColor(bool)), m_resourceManager, SIGNAL(sigEnableChangeColor(bool))); setCursor(Qt::ArrowCursor); setMouseTracking(true); setHoveredPreset(-1); setHoveredColor(-1); setSelectedColor(-1); m_brushHud = new KisBrushHud(provider, parent); m_brushHud->setMaximumHeight(m_popupPaletteSize); m_brushHud->setVisible(false); const int auxButtonSize = 35; m_settingsButton = new KisRoundHudButton(this); m_settingsButton->setIcon(KisIconUtils::loadIcon("configure")); m_settingsButton->setGeometry(m_popupPaletteSize - 2.2 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_settingsButton, SIGNAL(clicked()), SLOT(slotShowTagsPopup())); KisConfig cfg; m_brushHudButton = new KisRoundHudButton(this); m_brushHudButton->setCheckable(true); m_brushHudButton->setOnOffIcons(KisIconUtils::loadIcon("arrow-left"), KisIconUtils::loadIcon("arrow-right")); m_brushHudButton->setGeometry(m_popupPaletteSize - 1.0 * auxButtonSize, m_popupPaletteSize - auxButtonSize, auxButtonSize, auxButtonSize); connect(m_brushHudButton, SIGNAL(toggled(bool)), SLOT(showHudWidget(bool))); m_brushHudButton->setChecked(cfg.showBrushHud()); // add some stuff below the pop-up palette that will make it easier to use for tablet people QVBoxLayout* vLayout = new QVBoxLayout(this); // main layout QSpacerItem* verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); vLayout->addSpacerItem(verticalSpacer); // this should push the box to the bottom QHBoxLayout* hLayout = new QHBoxLayout(); vLayout->addLayout(hLayout); mirrorMode = new KisHighlightedToolButton(this); mirrorMode->setCheckable(true); mirrorMode->setFixedSize(35, 35); mirrorMode->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); mirrorMode->setToolTip(i18n("Mirror Canvas")); connect(mirrorMode, SIGNAL(clicked(bool)), this, SLOT(slotmirroModeClicked())); canvasOnlyButton = new KisHighlightedToolButton(this); canvasOnlyButton->setCheckable(true); canvasOnlyButton->setFixedSize(35, 35); canvasOnlyButton->setIcon(KisIconUtils::loadIcon("document-new")); canvasOnlyButton->setToolTip(i18n("Canvas Only")); connect(canvasOnlyButton, SIGNAL(clicked(bool)), this, SLOT(slotCanvasonlyModeClicked())); zoomToOneHundredPercentButton = new QPushButton(this); zoomToOneHundredPercentButton->setText(i18n("100%")); zoomToOneHundredPercentButton->setFixedHeight(35); zoomToOneHundredPercentButton->setIcon(KisIconUtils::loadIcon("zoom-original")); zoomToOneHundredPercentButton->setToolTip(i18n("Zoom to 100%")); connect(zoomToOneHundredPercentButton, SIGNAL(clicked(bool)), this, SLOT(slotZoomToOneHundredPercentClicked())); zoomCanvasSlider = new QSlider(Qt::Horizontal, this); zoomCanvasSlider->setRange(10, 200); // 10% to 200 % zoomCanvasSlider->setFixedHeight(35); zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); zoomCanvasSlider->setSingleStep(1); zoomCanvasSlider->setPageStep(1); connect(zoomCanvasSlider, SIGNAL(valueChanged(int)), this, SLOT(slotZoomSliderChanged(int))); hLayout->addWidget(mirrorMode); hLayout->addWidget(canvasOnlyButton); hLayout->addWidget(zoomToOneHundredPercentButton); hLayout->addWidget(zoomCanvasSlider); setVisible(true); setVisible(false); } void KisPopupPalette::slotExternalFgColorChanged(const KoColor &color) { - //m_triangleColorSelector->setRealColor(color); //hack to get around cmyk for now. if (color.colorSpace()->colorChannelCount()>3) { KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromKoColor(color); m_triangleColorSelector->slotSetColor(c); } else { m_triangleColorSelector->slotSetColor(color); } } void KisPopupPalette::slotEmitColorChanged() { if (isVisible()) { update(); emit sigChangefGColor(m_triangleColorSelector->getCurrentColor()); } } //setting KisPopupPalette properties int KisPopupPalette::hoveredPreset() const { return m_hoveredPreset; } void KisPopupPalette::setHoveredPreset(int x) { m_hoveredPreset = x; } int KisPopupPalette::hoveredColor() const { return m_hoveredColor; } void KisPopupPalette::setHoveredColor(int x) { m_hoveredColor = x; } int KisPopupPalette::selectedColor() const { return m_selectedColor; } void KisPopupPalette::setSelectedColor(int x) { m_selectedColor = x; } void KisPopupPalette::slotTriggerTimer() { m_timer.start(750); } void KisPopupPalette::slotEnableChangeFGColor() { emit sigEnableChangeFGColor(true); } void KisPopupPalette::slotZoomSliderChanged(int zoom) { emit zoomLevelChanged(zoom); } void KisPopupPalette::adjustLayout(const QPoint &p) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); if (isVisible() && parentWidget()) { float hudMargin = 30.0; const QRect fitRect = kisGrowRect(parentWidget()->rect(), -20.0); // -20 is widget margin const QPoint paletteCenterOffset(m_popupPaletteSize / 2, m_popupPaletteSize / 2); QRect paletteRect = rect(); paletteRect.moveTo(p - paletteCenterOffset); if (m_brushHudButton->isChecked()) { m_brushHud->updateGeometry(); paletteRect.adjust(0, 0, m_brushHud->width() + hudMargin, 0); } paletteRect = kisEnsureInRect(paletteRect, fitRect); move(paletteRect.topLeft()); m_brushHud->move(paletteRect.topLeft() + QPoint(m_popupPaletteSize + hudMargin, 0)); m_lastCenterPoint = p; } } void KisPopupPalette::showHudWidget(bool visible) { KIS_ASSERT_RECOVER_RETURN(m_brushHud); const bool reallyVisible = visible && m_brushHudButton->isChecked(); if (reallyVisible) { m_brushHud->updateProperties(); } m_brushHud->setVisible(reallyVisible); adjustLayout(m_lastCenterPoint); KisConfig cfg; cfg.setShowBrushHud(visible); } void KisPopupPalette::showPopupPalette(const QPoint &p) { showPopupPalette(!isVisible()); adjustLayout(p); } void KisPopupPalette::showPopupPalette(bool show) { if (show) { zoomCanvasSlider->setValue(m_coordinatesConverter->zoomInPercent()); // sync the zoom slider emit sigEnableChangeFGColor(!show); } else { emit sigTriggerTimer(); } setVisible(show); m_brushHud->setVisible(show && m_brushHudButton->isChecked()); } //redefinition of setVariable function to change the scope to private void KisPopupPalette::setVisible(bool b) { QWidget::setVisible(b); } void KisPopupPalette::setParent(QWidget *parent) { m_brushHud->setParent(parent); QWidget::setParent(parent); } QSize KisPopupPalette::sizeHint() const { return QSize(m_popupPaletteSize, m_popupPaletteSize + 50); // last number is the space for the toolbar below } void KisPopupPalette::resizeEvent(QResizeEvent*) { } void KisPopupPalette::paintEvent(QPaintEvent* e) { Q_UNUSED(e); QPainter painter(this); QPen pen(palette().color(QPalette::Text)); pen.setWidth(3); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); //painting background color indicator QPainterPath bgColor; bgColor.addEllipse(QPoint( 50, 80), 30, 30); painter.fillPath(bgColor, m_displayRenderer->toQColor(m_resourceManager->bgColor())); painter.drawPath(bgColor); //painting foreground color indicator QPainterPath fgColor; fgColor.addEllipse(QPoint( 60, 50), 30, 30); painter.fillPath(fgColor, m_displayRenderer->toQColor(m_triangleColorSelector->getCurrentColor())); painter.drawPath(fgColor); // create a circle background that everything else will go into QPainterPath backgroundContainer; float shrinkCircleAmount = 3;// helps the circle when the stroke is put around it QRectF circleRect(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); backgroundContainer.addEllipse( circleRect ); painter.fillPath(backgroundContainer,palette().brush(QPalette::Background)); painter.drawPath(backgroundContainer); // create a path slightly inside the container circle. this will create a 'track' to indicate that we can rotate the canvas // with the indicator QPainterPath rotationTrackPath; shrinkCircleAmount = 18; QRectF circleRect2(shrinkCircleAmount, shrinkCircleAmount, m_popupPaletteSize - shrinkCircleAmount*2,m_popupPaletteSize - shrinkCircleAmount*2); rotationTrackPath.addEllipse( circleRect2 ); pen.setWidth(1); painter.setPen(pen); painter.drawPath(rotationTrackPath); // this thing will help indicate where the starting brush preset is at. // also what direction they go to give sor order to the presets populated /* pen.setWidth(6); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.drawArc(circleRect, (16*90), (16*-30)); // span angle (last parameter) is in 16th of degrees QPainterPath brushDir; brushDir.arcMoveTo(circleRect, 60); brushDir.lineTo(brushDir.currentPosition().x()-5, brushDir.currentPosition().y() - 14); painter.drawPath(brushDir); brushDir.lineTo(brushDir.currentPosition().x()-2, brushDir.currentPosition().y() + 6); painter.drawPath(brushDir); */ // the following things needs to be based off the center, so let's translate the painter painter.translate(m_popupPaletteSize / 2, m_popupPaletteSize / 2); // create the canvas rotation handle QPainterPath rotationIndicator = drawRotationIndicator(m_coordinatesConverter->rotationAngle(), true); painter.fillPath(rotationIndicator,palette().brush(QPalette::Text)); // hover indicator for the canvas rotation if (m_isOverCanvasRotationIndicator == true) { painter.save(); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(2); painter.setPen(pen); painter.drawPath(rotationIndicator); painter.restore(); } // create a reset canvas rotation indicator to bring the canvas back to 0 degrees QPainterPath resetRotationIndicator = drawRotationIndicator(0, false); QPen resetPen(palette().color(QPalette::Text)); resetPen.setWidth(1); painter.save(); painter.setPen(resetPen); painter.drawPath(resetRotationIndicator); painter.restore(); //painting favorite brushes QList images(m_resourceManager->favoritePresetImages()); //painting favorite brushes pixmap/icon QPainterPath presetPath; for (int pos = 0; pos < numSlots(); pos++) { painter.save(); presetPath = createPathFromPresetIndex(pos); if (pos < images.size()) { painter.setClipPath(presetPath); QRect bounds = presetPath.boundingRect().toAlignedRect(); painter.drawImage(bounds.topLeft() , images.at(pos).scaled(bounds.size() , Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { painter.fillPath(presetPath, palette().brush(QPalette::Window)); // brush slot that has no brush in it } QPen pen = painter.pen(); pen.setWidth(1); painter.setPen(pen); painter.drawPath(presetPath); painter.restore(); } if (hoveredPreset() > -1) { presetPath = createPathFromPresetIndex(hoveredPreset()); QPen pen(palette().color(QPalette::Highlight)); pen.setWidth(3); painter.setPen(pen); painter.drawPath(presetPath); } // paint recent colors area. painter.setPen(Qt::NoPen); float rotationAngle = -360.0 / m_resourceManager->recentColorsTotal(); // there might be no recent colors at the start, so paint a placeholder if (m_resourceManager->recentColorsTotal() == 0) { painter.setBrush(Qt::transparent); QPainterPath emptyRecentColorsPath(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.setPen(QPen(palette().color(QPalette::Background).lighter(150), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); painter.drawPath(emptyRecentColorsPath); } else { for (int pos = 0; pos < m_resourceManager->recentColorsTotal(); pos++) { QPainterPath recentColorsPath(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); //accessing recent color of index pos painter.fillPath(recentColorsPath, m_displayRenderer->toQColor( m_resourceManager->recentColorAt(pos) )); painter.drawPath(recentColorsPath); painter.rotate(rotationAngle); } } //painting hovered color if (hoveredColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + hoveredColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(hoveredColor() * -1 * rotationAngle); } } //painting selected color if (selectedColor() > -1) { painter.setPen(QPen(palette().color(QPalette::Highlight).darker(130), 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); if (m_resourceManager->recentColorsTotal() == 1) { QPainterPath path_ColorDonut(drawDonutPathFull(0, 0, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); painter.drawPath(path_ColorDonut); } else { painter.rotate((m_resourceManager->recentColorsTotal() + selectedColor()) *rotationAngle); QPainterPath path(drawDonutPathAngle(m_colorHistoryInnerRadius, m_colorHistoryOuterRadius, m_resourceManager->recentColorsTotal())); painter.drawPath(path); painter.rotate(selectedColor() * -1 * rotationAngle); } } } QPainterPath KisPopupPalette::drawDonutPathFull(int x, int y, int inner_radius, int outer_radius) { QPainterPath path; path.addEllipse(QPointF(x, y), outer_radius, outer_radius); path.addEllipse(QPointF(x, y), inner_radius, inner_radius); path.setFillRule(Qt::OddEvenFill); return path; } QPainterPath KisPopupPalette::drawDonutPathAngle(int inner_radius, int outer_radius, int limit) { QPainterPath path; path.moveTo(-0.999 * outer_radius * sin(M_PI / limit), 0.999 * outer_radius * cos(M_PI / limit)); path.arcTo(-1 * outer_radius, -1 * outer_radius, 2 * outer_radius, 2 * outer_radius, -90.0 - 180.0 / limit, 360.0 / limit); path.arcTo(-1 * inner_radius, -1 * inner_radius, 2 * inner_radius, 2 * inner_radius, -90.0 + 180.0 / limit, - 360.0 / limit); path.closeSubpath(); return path; } QPainterPath KisPopupPalette::drawRotationIndicator(qreal rotationAngle, bool canDrag) { // used for canvas rotation. This function gets called twice. Once by the canvas rotation indicator, // and another time by the reset canvas position float canvasRotationRadians = qDegreesToRadians(rotationAngle - 90); // -90 will make 0 degrees be at the top float rotationDialXPosition = qCos(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); // m_popupPaletteSize/2 = radius float rotationDialYPosition = qSin(canvasRotationRadians) * (m_popupPaletteSize/2 - 10); QPainterPath canvasRotationIndicator; int canvasIndicatorSize = 15; float canvasIndicatorMiddle = canvasIndicatorSize/2; QRect indicatorRectangle = QRect( rotationDialXPosition - canvasIndicatorMiddle, rotationDialYPosition - canvasIndicatorMiddle, canvasIndicatorSize, canvasIndicatorSize ); if (canDrag) { m_canvasRotationIndicatorRect = indicatorRectangle; } else { m_resetCanvasRotationIndicatorRect = indicatorRectangle; } canvasRotationIndicator.addEllipse(indicatorRectangle.x(), indicatorRectangle.y(), indicatorRectangle.width(), indicatorRectangle.height() ); return canvasRotationIndicator; } void KisPopupPalette::mouseMoveEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); setToolTip(QString()); setHoveredPreset(-1); setHoveredColor(-1); // calculate if we are over the canvas rotation knob // before we started painting, we moved the painter to the center of the widget, so the X/Y positions are offset. we need to // correct them first before looking for a click event intersection float rotationCorrectedXPos = m_canvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_canvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_canvasRotationIndicatorRect.width(), m_canvasRotationIndicatorRect.height()); if (correctedCanvasRotationIndicator.contains(point.x(), point.y())) { m_isOverCanvasRotationIndicator = true; } else { m_isOverCanvasRotationIndicator = false; } if (m_isRotatingCanvasIndicator) { // we are rotating the canvas, so calculate the rotation angle based off the center // calculate the angle we are at first QPoint widgetCenterPoint = QPoint(m_popupPaletteSize/2, m_popupPaletteSize/2); float dX = point.x() - widgetCenterPoint.x(); float dY = point.y() - widgetCenterPoint.y(); float finalAngle = qAtan2(dY,dX) * 180 / M_PI; // what we need if we have two points, but don't know the angle finalAngle = finalAngle + 90; // add 90 degrees so 0 degree position points up float angleDifference = finalAngle - m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it out m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } // don't highlight the presets if we are in the middle of rotating the canvas if (m_isRotatingCanvasIndicator == false) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); { int pos = calculatePresetIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets()) { setToolTip(m_resourceManager->favoritePresetList().at(pos).data()->name()); setHoveredPreset(pos); } } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { setHoveredColor(pos); } } } update(); } void KisPopupPalette::mousePressEvent(QMouseEvent* event) { QPointF point = event->posF(); event->accept(); if (event->button() == Qt::LeftButton) { //in favorite brushes area int pos = calculateIndex(point, m_resourceManager->numFavoritePresets()); if (pos >= 0 && pos < m_resourceManager->numFavoritePresets() && isPointInPixmap(point, pos)) { //setSelectedBrush(pos); update(); } if (m_isOverCanvasRotationIndicator) { m_isRotatingCanvasIndicator = true; } // reset the canvas if we are over the reset canvas rotation indicator float rotationCorrectedXPos = m_resetCanvasRotationIndicatorRect.x() + (m_popupPaletteSize / 2); float rotationCorrectedYPos = m_resetCanvasRotationIndicatorRect.y() + (m_popupPaletteSize / 2); QRect correctedResetCanvasRotationIndicator = QRect(rotationCorrectedXPos, rotationCorrectedYPos, m_resetCanvasRotationIndicatorRect.width(), m_resetCanvasRotationIndicatorRect.height()); if (correctedResetCanvasRotationIndicator.contains(point.x(), point.y())) { float angleDifference = -m_coordinatesConverter->rotationAngle(); // the rotation function accepts diffs, so find it ou m_coordinatesConverter->rotate(m_coordinatesConverter->widgetCenterPoint(), angleDifference); emit sigUpdateCanvas(); } } } void KisPopupPalette::slotShowTagsPopup() { KisPaintOpPresetResourceServer* rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); QStringList tags = rServer->tagNamesList(); qSort(tags); if (!tags.isEmpty()) { QMenu menu; Q_FOREACH (const QString& tag, tags) { menu.addAction(tag); } QAction* action = menu.exec(QCursor::pos()); if (action) { m_resourceManager->setCurrentTag(action->text()); } } else { QWhatsThis::showText(QCursor::pos(), i18n("There are no tags available to show in this popup. To add presets, you need to tag them and then select the tag here.")); } } void KisPopupPalette::slotmirroModeClicked() { QAction* action = m_actionCollection->action("mirror_canvas"); if (action) { action->trigger(); } } void KisPopupPalette::slotCanvasonlyModeClicked() { QAction* action = m_actionCollection->action("view_show_canvas_only"); if (action) { action->trigger(); } } void KisPopupPalette::slotZoomToOneHundredPercentClicked() { QAction* action = m_actionCollection->action("zoom_to_100pct"); if (action) { action->trigger(); } } void KisPopupPalette::tabletEvent(QTabletEvent* /*event*/) { } void KisPopupPalette::mouseReleaseEvent(QMouseEvent * event) { QPointF point = event->posF(); event->accept(); m_isOverCanvasRotationIndicator = false; m_isRotatingCanvasIndicator = false; if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { QPainterPath pathColor(drawDonutPathFull(m_popupPaletteSize / 2, m_popupPaletteSize / 2, m_colorHistoryInnerRadius, m_colorHistoryOuterRadius)); //in favorite brushes area if (hoveredPreset() > -1) { //setSelectedBrush(hoveredBrush()); emit sigChangeActivePaintop(hoveredPreset()); } if (pathColor.contains(point)) { int pos = calculateIndex(point, m_resourceManager->recentColorsTotal()); if (pos >= 0 && pos < m_resourceManager->recentColorsTotal()) { emit sigUpdateRecentColor(pos); } } } } int KisPopupPalette::calculateIndex(QPointF point, int n) { calculatePresetIndex(point, n); //translate to (0,0) point.setX(point.x() - m_popupPaletteSize / 2); point.setY(point.y() - m_popupPaletteSize / 2); //rotate float smallerAngle = M_PI / 2 + M_PI / n - atan2(point.y(), point.x()); float radius = sqrt((float)point.x() * point.x() + point.y() * point.y()); point.setX(radius * cos(smallerAngle)); point.setY(radius * sin(smallerAngle)); //calculate brush index int pos = floor(acos(point.x() / radius) * n / (2 * M_PI)); if (point.y() < 0) pos = n - pos - 1; return pos; } bool KisPopupPalette::isPointInPixmap(QPointF& point, int pos) { if (createPathFromPresetIndex(pos).contains(point + QPointF(-m_popupPaletteSize / 2, -m_popupPaletteSize / 2))) { return true; } return false; } KisPopupPalette::~KisPopupPalette() { } QPainterPath KisPopupPalette::createPathFromPresetIndex(int index) { qreal angleSlice = 360.0 / numSlots() ; // how many degrees each slice will get // the starting angle of the slice we need to draw. the negative sign makes us go clockwise. // adding 90 degrees makes us start at the top. otherwise we would start at the right qreal startingAngle = -(index * angleSlice) + 90; // the radius will get smaller as the amount of presets shown increases. 10 slots == 41 qreal presetRadius = m_colorHistoryOuterRadius * qSin(qDegreesToRadians(angleSlice/2)) / (1-qSin(qDegreesToRadians(angleSlice/2))); QPainterPath path; float pathX = (m_colorHistoryOuterRadius + presetRadius) * qCos(qDegreesToRadians(startingAngle)) - presetRadius; float pathY = -(m_colorHistoryOuterRadius + presetRadius) * qSin(qDegreesToRadians(startingAngle)) - presetRadius; float pathDiameter = 2 * presetRadius; // distance is used to calculate the X/Y in addition to the preset circle size path.addEllipse(pathX, pathY, pathDiameter, pathDiameter); return path; } int KisPopupPalette::calculatePresetIndex(QPointF point, int /*n*/) { for(int i = 0; i < numSlots(); i++) { QPointF adujustedPoint = point - QPointF(m_popupPaletteSize/2, m_popupPaletteSize/2); if(createPathFromPresetIndex(i).contains(adujustedPoint)) { return i; } } return -1; } int KisPopupPalette::numSlots() { KisConfig config; return qMax(config.favoritePresets(), 10); } diff --git a/libs/ui/kis_popup_palette.h b/libs/ui/kis_popup_palette.h index 22b490348c..1aa922c1ee 100644 --- a/libs/ui/kis_popup_palette.h +++ b/libs/ui/kis_popup_palette.h @@ -1,176 +1,176 @@ /* This file is part of the KDE project Copyright 2009 Vera Lukman Copyright 2016 Scott Petrovic This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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_POPUP_PALETTE_H #define KIS_POPUP_PALETTE_H #include #include #include #include #include #include #include "kis_action_manager.h" #include "KisViewManager.h" #include "kactioncollection.h" #include "kis_coordinates_converter.h" #include "kis_tool_button.h" #include "kis_highlighted_button.h" - +#include class KisFavoriteResourceManager; class QWidget; class KoColor; class KoTriangleColorSelector; class KisSignalCompressor; class KisBrushHud; class KisRoundHudButton; class KisCanvasResourceProvider; class KisVisualColorSelector; class KisPopupPalette : public QWidget { Q_OBJECT Q_PROPERTY(int hoveredPreset READ hoveredPreset WRITE setHoveredPreset) Q_PROPERTY(int hoveredColor READ hoveredColor WRITE setHoveredColor) Q_PROPERTY(int selectedColor READ selectedColor WRITE setSelectedColor) public: KisPopupPalette(KisViewManager*, KisCoordinatesConverter* ,KisFavoriteResourceManager*, const KoColorDisplayRendererInterface *displayRenderer, KisCanvasResourceProvider *provider, QWidget *parent = 0); ~KisPopupPalette(); QSize sizeHint() const; void showPopupPalette(const QPoint&); void showPopupPalette(bool b); //functions to set up selectedBrush void setSelectedBrush(int x); int selectedBrush() const; //functions to set up selectedColor void setSelectedColor(int x); int selectedColor() const; void setParent(QWidget *parent); virtual void tabletEvent(QTabletEvent * event); protected: void paintEvent(QPaintEvent*); void resizeEvent(QResizeEvent*); void mouseReleaseEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void mousePressEvent(QMouseEvent*); //functions to calculate index of favorite brush or recent color in array //n is the total number of favorite brushes or recent colors int calculateIndex(QPointF, int n); int calculatePresetIndex(QPointF, int n); //functions to set up hoveredBrush void setHoveredPreset(int x); int hoveredPreset() const; //functions to set up hoveredColor void setHoveredColor(int x); int hoveredColor() const; private: void setVisible(bool b); QPainterPath drawDonutPathFull(int, int, int, int); QPainterPath drawDonutPathAngle(int, int, int); QPainterPath drawRotationIndicator(qreal rotationAngle, bool canDrag); bool isPointInPixmap(QPointF&, int pos); QPainterPath createPathFromPresetIndex(int index); int numSlots(); void adjustLayout(const QPoint &p); private: int m_hoveredPreset; int m_hoveredColor; int m_selectedColor; KisCoordinatesConverter* m_coordinatesConverter; KisActionManager* m_actionManager; KisFavoriteResourceManager* m_resourceManager; - KisVisualColorSelector* m_triangleColorSelector; + KisColorSelectorInterface* m_triangleColorSelector {0}; const KoColorDisplayRendererInterface *m_displayRenderer; QScopedPointer m_colorChangeCompressor; KActionCollection* m_actionCollection; QTimer m_timer; KisBrushHud *m_brushHud; float m_popupPaletteSize; float m_colorHistoryInnerRadius; float m_colorHistoryOuterRadius; KisRoundHudButton *m_settingsButton; KisRoundHudButton *m_brushHudButton; QPoint m_lastCenterPoint; QRect m_canvasRotationIndicatorRect; QRect m_resetCanvasRotationIndicatorRect; bool m_isOverCanvasRotationIndicator; bool m_isRotatingCanvasIndicator; KisHighlightedToolButton* mirrorMode; KisHighlightedToolButton* canvasOnlyButton; QPushButton* zoomToOneHundredPercentButton; QSlider* zoomCanvasSlider; Q_SIGNALS: void sigChangeActivePaintop(int); void sigUpdateRecentColor(int); void sigChangefGColor(const KoColor&); void sigUpdateCanvas(); void zoomLevelChanged(int); // These are used to handle a bug: // If pop up palette is visible and a new colour is selected, the new colour // will be added when the user clicks on the canvas to hide the palette // In general, we want to be able to store recent color if the pop up palette // is not visible void sigEnableChangeFGColor(bool); void sigTriggerTimer(); private Q_SLOTS: void slotExternalFgColorChanged(const KoColor &color); void slotEmitColorChanged(); void slotSetSelectedColor(int x) { setSelectedColor(x); update(); } void slotTriggerTimer(); void slotEnableChangeFGColor(); void slotUpdate() { update(); } void slotHide() { showPopupPalette(false); } void slotShowTagsPopup(); void showHudWidget(bool visible); void slotmirroModeClicked(); void slotCanvasonlyModeClicked(); void slotZoomToOneHundredPercentClicked(); void slotZoomSliderChanged(int zoom); }; #endif // KIS_POPUP_PALETTE_H diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc index b8349dd422..9d58bfd21e 100644 --- a/libs/ui/kis_zoom_manager.cc +++ b/libs/ui/kis_zoom_manager.cc @@ -1,334 +1,334 @@ /* * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2009 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_zoom_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "kis_image.h" #include "kis_statusbar.h" #include "kis_config.h" #include "krita_utils.h" #include "kis_canvas_resource_provider.h" #include "kis_lod_transform.h" #include "kis_snap_line_strategy.h" class KisZoomController : public KoZoomController { public: KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons, QObject *parent) - : KoZoomController(co, zh, actionCollection, false, specialButtons, parent), + : KoZoomController(co, zh, actionCollection, specialButtons, parent), m_converter(zh) { } protected: QSize documentToViewport(const QSizeF &size) override { QRectF docRect(QPointF(), size); return m_converter->documentToWidget(docRect).toRect().size(); } private: KisCoordinatesConverter *m_converter; }; KisZoomManager::KisZoomManager(QPointer view, KoZoomHandler * zoomHandler, KoCanvasController * canvasController) : m_view(view) , m_zoomHandler(zoomHandler) , m_canvasController(canvasController) , m_horizontalRuler(0) , m_verticalRuler(0) , m_zoomAction(0) , m_zoomActionWidget(0) { } KisZoomManager::~KisZoomManager() { if (m_zoomActionWidget && !m_zoomActionWidget->parent()) { delete m_zoomActionWidget; } } void KisZoomManager::setup(KActionCollection * actionCollection) { KisImageWSP image = m_view->image(); if (!image) return; connect(image, SIGNAL(sigSizeChanged(const QPointF &, const QPointF &)), this, SLOT(setMinMaxZoom())); KisCoordinatesConverter *converter = dynamic_cast(m_zoomHandler); m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, KoZoomAction::AspectMode, this); m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS); m_zoomHandler->setZoom(1.0); m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes())); m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true); m_zoomAction = m_zoomController->zoomAction(); setMinMaxZoom(); m_zoomActionWidget = m_zoomAction->createWidget(0); // Put the canvascontroller in a layout so it resizes with us QGridLayout * layout = new QGridLayout(m_view); layout->setSpacing(0); layout->setMargin(0); m_view->setLayout(layout); m_view->document()->setUnit(KoUnit(KoUnit::Pixel)); m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler); m_horizontalRuler->setShowMousePosition(true); m_horizontalRuler->createGuideToolConnection(m_view->canvasBase()); m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler); m_verticalRuler->setShowMousePosition(true); m_verticalRuler->createGuideToolConnection(m_view->canvasBase()); m_verticalRuler->setVisible(false); QList unitActions = m_view->createChangeUnitActions(true); m_horizontalRuler->setPopupActionList(unitActions); m_verticalRuler->setPopupActionList(unitActions); connect(m_view->document(), SIGNAL(unitChanged(const KoUnit&)), SLOT(applyRulersUnit(const KoUnit&))); layout->addWidget(m_horizontalRuler, 0, 1); layout->addWidget(m_verticalRuler, 1, 0); layout->addWidget(static_cast(m_canvasController), 1, 1); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode, qreal)), this, SLOT(slotZoomChanged(KoZoomMode::Mode, qreal))); connect(m_zoomController, SIGNAL(aspectModeChanged(bool)), this, SLOT(changeAspectMode(bool))); applyRulersUnit(m_view->document()->unit()); } void KisZoomManager::updateImageBoundsSnapping() { const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels(); const QPointF docCenter = docRect.center(); KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide(); { KisSnapLineStrategy *boundsSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping); boundsSnap->addLine(Qt::Horizontal, docRect.y()); boundsSnap->addLine(Qt::Horizontal, docRect.bottom()); boundsSnap->addLine(Qt::Vertical, docRect.x()); boundsSnap->addLine(Qt::Vertical, docRect.right()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap); } { KisSnapLineStrategy *centerSnap = new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping); centerSnap->addLine(Qt::Horizontal, docCenter.y()); centerSnap->addLine(Qt::Vertical, docCenter.x()); snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap); } } void KisZoomManager::updateMouseTrackingConnections() { bool value = m_horizontalRuler->isVisible() && m_verticalRuler->isVisible() && m_horizontalRuler->showMousePosition() && m_verticalRuler->showMousePosition(); m_mouseTrackingConnections.clear(); if (value) { connect(m_canvasController->proxyObject, SIGNAL(canvasMousePositionChanged(const QPoint &)), SLOT(mousePositionChanged(const QPoint &))); } } KoRuler* KisZoomManager::horizontalRuler() const { return m_horizontalRuler; } KoRuler* KisZoomManager::verticalRuler() const { return m_verticalRuler; } void KisZoomManager::mousePositionChanged(const QPoint &viewPos) { QPoint pt = viewPos - m_rulersOffset; m_horizontalRuler->updateMouseCoordinate(pt.x()); m_verticalRuler->updateMouseCoordinate(pt.y()); } void KisZoomManager::setShowRulers(bool show) { m_horizontalRuler->setVisible(show); m_verticalRuler->setVisible(show); updateMouseTrackingConnections(); } void KisZoomManager::setRulersTrackMouse(bool value) { m_horizontalRuler->setShowMousePosition(value); m_verticalRuler->setShowMousePosition(value); updateMouseTrackingConnections(); } void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit) { m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes())); m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes())); } void KisZoomManager::setMinMaxZoom() { KisImageWSP image = m_view->image(); if (!image) return; QSize imageSize = image->size(); qreal minDimension = qMin(imageSize.width(), imageSize.height()); qreal minZoom = qMin(100.0 / minDimension, 0.1); m_zoomAction->setMinimumZoom(minZoom); m_zoomAction->setMaximumZoom(90.0); } void KisZoomManager::updateGUI() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size(); m_horizontalRuler->setRulerLength(documentSize.width()); m_verticalRuler->setRulerLength(documentSize.height()); applyRulersUnit(m_horizontalRuler->unit()); } QWidget *KisZoomManager::zoomActionWidget() const { return m_zoomActionWidget; } void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom) { Q_UNUSED(mode); Q_UNUSED(zoom); m_view->canvasBase()->notifyZoomChanged(); qreal humanZoom = zoom * 100.0; // XXX: KOMVC -- this is very irritating in MDI mode if (m_view->viewManager()) { m_view->viewManager()-> showFloatingMessage( i18nc("floating message about zoom", "Zoom: %1 %", KritaUtils::prettyFormatReal(humanZoom)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } const qreal effectiveZoom = m_view->canvasBase()->coordinatesConverter()->effectiveZoom(); m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom); } void KisZoomManager::slotScrollAreaSizeChanged() { pageOffsetChanged(); updateGUI(); } void KisZoomManager::changeAspectMode(bool aspectMode) { KisImageWSP image = m_view->image(); KoZoomMode::Mode newMode = KoZoomMode::ZOOM_CONSTANT; qreal newZoom = m_zoomHandler->zoom(); qreal resolutionX = aspectMode ? image->xRes() : POINT_TO_INCH(static_cast(KoDpi::dpiX())); qreal resolutionY = aspectMode ? image->yRes() : POINT_TO_INCH(static_cast(KoDpi::dpiY())); m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY); m_view->canvasBase()->notifyZoomChanged(); } void KisZoomManager::pageOffsetChanged() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); m_rulersOffset = widgetRect.topLeft().toPoint(); m_horizontalRuler->setOffset(m_rulersOffset.x()); m_verticalRuler->setOffset(m_rulersOffset.y()); } void KisZoomManager::zoomTo100() { m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); m_view->canvasBase()->notifyZoomChanged(); } diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc index 45b3814c78..5eb901ff1d 100644 --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -1,694 +1,698 @@ /* * Copyright (c) 2006, 2010 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "opengl/kis_opengl_canvas2.h" #include "kis_canvas_resource_provider.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "filter/kis_filter_configuration.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_cursor.h" #include #include #include "kis_resources_snapshot.h" #include #include "kis_action_registry.h" #include "kis_tool_utils.h" struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. // From the canvas resources KoPattern* currentPattern{0}; KoAbstractGradient* currentGradient{0}; KoColor currentFgColor; KoColor currentBgColor; float currentExposure{1.0}; KisFilterConfigurationSP currentGenerator; QWidget* optionWidget{0}; ToolMode m_mode{HOVER_MODE}; bool m_isActive{false}; }; KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor) : KoToolBase(canvas) , d(new Private) { d->cursor = cursor; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle())); connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle())); KActionCollection *collection = this->canvas()->canvasController()->actionCollection(); if (!collection->action("toggle_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection); collection->addAction("toggle_fg_bg", toggleFgBg); } if (!collection->action("reset_fg_bg")) { QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection); collection->addAction("reset_fg_bg", toggleFgBg); } addAction("toggle_fg_bg", dynamic_cast(collection->action("toggle_fg_bg"))); addAction("reset_fg_bg", dynamic_cast(collection->action("reset_fg_bg"))); } KisTool::~KisTool() { delete d; } void KisTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); resetCursorStyle(); if (!canvas()) return; if (!canvas()->resourceManager()) return; d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value(); d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value(); if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) { d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) { d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value(); } KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && preset->settings()) { preset->settings()->activate(); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) { d->currentExposure = static_cast(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble()); } if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) { d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value(); } connect(actions().value("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection); connect(actions().value("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection); connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection); connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection); d->m_isActive = true; emit isActiveChanged(); } void KisTool::deactivate() { bool result = true; result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0); result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0); result &= disconnect(actions().value("toggle_fg_bg"), 0, this, 0); result &= disconnect(actions().value("reset_fg_bg"), 0, this, 0); if (!result) { warnKrita << "WARNING: KisTool::deactivate() failed to disconnect" << "some signal connections. Your actions might be executed twice!"; } d->m_isActive = false; emit isActiveChanged(); } void KisTool::requestUndoDuringStroke() { /** * Default implementation just cancells the stroke */ requestStrokeCancellation(); } void KisTool::requestStrokeCancellation() { } void KisTool::requestStrokeEnd() { } void KisTool::canvasResourceChanged(int key, const QVariant & v) { switch (key) { case(KoCanvasResourceManager::ForegroundColor): d->currentFgColor = v.value(); break; case(KoCanvasResourceManager::BackgroundColor): d->currentBgColor = v.value(); break; case(KisCanvasResourceProvider::CurrentPattern): d->currentPattern = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentGradient): d->currentGradient = static_cast(v.value()); break; case(KisCanvasResourceProvider::HdrExposure): d->currentExposure = static_cast(v.toDouble()); break; case(KisCanvasResourceProvider::CurrentGeneratorConfiguration): d->currentGenerator = static_cast(v.value()); break; case(KisCanvasResourceProvider::CurrentPaintOpPreset): emit statusTextChanged(v.value()->name()); break; case(KisCanvasResourceProvider::CurrentKritaNode): resetCursorStyle(); break; default: break; // Do nothing }; } void KisTool::updateSettingsViews() { } QPointF KisTool::widgetCenterInWidgetPixels() { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter(); return converter->flakeToWidget(converter->flakeCenterPoint()); } QPointF KisTool::convertDocumentToWidget(const QPointF& pt) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->coordinatesConverter()->documentToWidget(pt); } QPointF KisTool::convertToPixelCoord(KoPointerEvent *e) { if (!image()) return e->point; return image()->documentToPixel(e->point); } QPointF KisTool::convertToPixelCoord(const QPointF& pt) { if (!image()) return pt; return image()->documentToPixel(pt); } QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!image()) return e->point; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); return image()->documentToPixel(pos); } QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset) { if (!image()) return pt; KoSnapGuide *snapGuide = canvas()->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return image()->documentToPixel(pos); } QPoint KisTool::convertToIntPixelCoord(KoPointerEvent *e) { if (!image()) return e->point.toPoint(); return image()->documentToIntPixel(e->point); } QPointF KisTool::viewToPixel(const QPointF &viewCoord) const { if (!image()) return viewCoord; return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord)); } QRectF KisTool::convertToPt(const QRectF &rect) { if (!image()) return rect; QRectF r; //We add 1 in the following to the extreme coords because a pixel always has size r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(), int(1 + rect.right()) / image()->xRes(), int(1 + rect.bottom()) / image()->yRes()); return r; } QPointF KisTool::pixelToView(const QPoint &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QPointF KisTool::pixelToView(const QPointF &pixelCoord) const { if (!image()) return pixelCoord; QPointF documentCoord = image()->pixelToDocument(pixelCoord); return canvas()->viewConverter()->documentToView(documentCoord); } QRectF KisTool::pixelToView(const QRectF &pixelRect) const { if (!image()) return pixelRect; QPointF topLeft = pixelToView(pixelRect.topLeft()); QPointF bottomRight = pixelToView(pixelRect.bottomRight()); return QRectF(topLeft, bottomRight); } QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPolygon); } QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const { QTransform matrix; qreal zoomX, zoomY; canvas()->viewConverter()->zoom(&zoomX, &zoomY); matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes()); return matrix.map(pixelPath); } void KisTool::updateCanvasPixelRect(const QRectF &pixelRect) { canvas()->updateCanvas(convertToPt(pixelRect)); } void KisTool::updateCanvasViewRect(const QRectF &viewRect) { canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect)); } KisImageWSP KisTool::image() const { // For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1 KisCanvas2 * kisCanvas = dynamic_cast(canvas()); if (kisCanvas) { return kisCanvas->currentImage(); } return 0; } QCursor KisTool::cursor() const { return d->cursor; } void KisTool::notifyModified() const { if (image()) { image()->setModified(); } } KoPattern * KisTool::currentPattern() { return d->currentPattern; } KoAbstractGradient * KisTool::currentGradient() { return d->currentGradient; } KisPaintOpPresetSP KisTool::currentPaintOpPreset() { return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); } KisNodeSP KisTool::currentNode() const { KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); return node; } KisNodeList KisTool::selectedNodes() const { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); return viewManager->nodeManager()->selectedNodes(); } KoColor KisTool::currentFgColor() { return d->currentFgColor; } KoColor KisTool::currentBgColor() { return d->currentBgColor; } KisImageWSP KisTool::currentImage() { return image(); } KisFilterConfigurationSP KisTool::currentGenerator() { return d->currentGenerator; } void KisTool::setMode(ToolMode mode) { d->m_mode = mode; } KisTool::ToolMode KisTool::mode() const { return d->m_mode; } KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) { KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary); return (AlternateAction)action; } void KisTool::activatePrimaryAction() { resetCursorStyle(); } void KisTool::deactivatePrimaryAction() { resetCursorStyle(); } void KisTool::beginPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event) { beginPrimaryAction(event); } void KisTool::continuePrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); } bool KisTool::primaryActionSupportsHiResEvents() const { return false; } void KisTool::activateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::deactivateAlternateAction(AlternateAction action) { Q_UNUSED(action); } void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action) { beginAlternateAction(event, action); } void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } void KisTool::mouseDoubleClickEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseTripleClickEvent(KoPointerEvent *event) { mouseDoubleClickEvent(event); } void KisTool::mousePressEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseReleaseEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::mouseMoveEvent(KoPointerEvent *event) { Q_UNUSED(event); } void KisTool::deleteSelection() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); + KisCanvas2 * kiscanvas = static_cast(canvas()); + KisViewManager* viewManager = kiscanvas->viewManager(); + viewManager->blockUntillOperationsFinished(image()); + if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) { KoToolBase::deleteSelection(); } } void KisTool::setupPaintAction(KisRecordedPaintAction* action) { action->setPaintColor(currentFgColor()); action->setBackgroundColor(currentBgColor()); } QWidget* KisTool::createOptionWidget() { d->optionWidget = new QLabel(i18n("No options")); d->optionWidget->setObjectName("SpecialSpacer"); return d->optionWidget; } #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #define PROGRAM_VERTEX_ATTRIBUTE 0 void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path) { KisOpenGLCanvas2 *canvasWidget = dynamic_cast(canvas()->canvasWidget()); if (canvasWidget) { painter->beginNativePainting(); canvasWidget->paintToolOutline(path); painter->endNativePainting(); } else { painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination); painter->setPen(QColor(128, 255, 128)); painter->drawPath(path); } } void KisTool::resetCursorStyle() { useCursor(d->cursor); } bool KisTool::overrideCursorIfNotEditable() { // override cursor for canvas iff this tool is active // and we can't paint on the active layer if (isActive()) { KisNodeSP node = currentNode(); if (node && !node->isEditable()) { canvas()->setCursor(Qt::ForbiddenCursor); return true; } } return false; } bool KisTool::isActive() const { return d->m_isActive; } void KisTool::slotToggleFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); KoColor newFg = resourceManager->backgroundColor(); KoColor newBg = resourceManager->foregroundColor(); /** * NOTE: Some of color selectors do not differentiate foreground * and background colors, so if one wants them to end up * being set up to foreground color, it should be set the * last. */ resourceManager->setBackgroundColor(newBg); resourceManager->setForegroundColor(newFg); } void KisTool::slotResetFgBg() { KoCanvasResourceManager* resourceManager = canvas()->resourceManager(); // see a comment in slotToggleFgBg() resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8())); resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8())); } void KisTool::setCurrentNodeLocked(bool locked) { if (currentNode()) { currentNode()->setSystemLocked(locked, false); } } bool KisTool::nodeEditable() { KisNodeSP node = currentNode(); if (!node) { return false; } bool nodeEditable = node->isEditable(); if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { message = i18n("Layer is locked and invisible."); } else if (node->userLocked()) { message = i18n("Layer is locked."); } else if(!node->visible()) { message = i18n("Layer is invisible."); } else { message = i18n("Group not editable."); } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } return nodeEditable; } bool KisTool::selectionEditable() { KisCanvas2 * kisCanvas = static_cast(canvas()); KisViewManager * view = kisCanvas->viewManager(); bool editable = view->selectionEditable(); if (!editable) { KisCanvas2 * kiscanvas = static_cast(canvas()); kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked")); } return editable; } void KisTool::listenToModifiers(bool listen) { Q_UNUSED(listen); } bool KisTool::listeningToModifiers() { return false; } diff --git a/libs/ui/widgets/KisVisualColorSelector.cpp b/libs/ui/widgets/KisVisualColorSelector.cpp index 5ad8c1cb5f..70bc2ae838 100644 --- a/libs/ui/widgets/KisVisualColorSelector.cpp +++ b/libs/ui/widgets/KisVisualColorSelector.cpp @@ -1,442 +1,442 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualColorSelector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "KisVisualColorSelectorShape.h" #include "KisVisualRectangleSelectorShape.h" #include "KisVisualTriangleSelectorShape.h" #include "KisVisualEllipticalSelectorShape.h" struct KisVisualColorSelector::Private { KoColor currentcolor; const KoColorSpace *currentCS {0}; QList widgetlist; bool updateSelf {false}; bool updateLonesome {false}; // for modal dialogs. bool circular {false}; const KoColorDisplayRendererInterface *displayRenderer {0}; KisColorSelectorConfiguration acs_config; KisSignalCompressor *updateTimer {0}; }; KisVisualColorSelector::KisVisualColorSelector(QWidget *parent) - : QWidget(parent) + : KisColorSelectorInterface(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); QVBoxLayout *layout = new QVBoxLayout; this->setLayout(layout); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection); } KisVisualColorSelector::~KisVisualColorSelector() { delete m_d->updateTimer; } -void KisVisualColorSelector::slotSetColor(KoColor c) +void KisVisualColorSelector::slotSetColor(const KoColor &c) { if (m_d->updateSelf == false) { m_d->currentcolor = c; if (m_d->currentCS != c.colorSpace()) { slotsetColorSpace(c.colorSpace()); } } updateSelectorElements(QObject::sender()); } void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs) { if (m_d->currentCS != cs) { m_d->currentCS = cs; slotRebuildSelectors(); } } void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate) { m_d->circular = forceCircular; m_d->updateLonesome = forceSelfUpdate; } -KoColor KisVisualColorSelector::getCurrentColor() +KoColor KisVisualColorSelector::getCurrentColor() const { return m_d->currentcolor; } void KisVisualColorSelector::configurationChanged() { if (m_d->updateTimer) { m_d->updateTimer->start(); } } void KisVisualColorSelector::slotRebuildSelectors() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); qDeleteAll(children()); m_d->widgetlist.clear(); QLayout *layout = new QHBoxLayout; //redraw all the widgets. int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); if (m_d->currentCS->colorChannelCount() == 1) { KisVisualColorSelectorShape *bar; if (m_d->circular==false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, borderWidth); bar->setMaximumWidth(width()*0.1); bar->setMaximumHeight(height()); } else { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 0,m_d->displayRenderer, borderWidth, KisVisualEllipticalSelectorShape::borderMirrored); layout->setMargin(0); } connect (bar, SIGNAL(sigNewColor(KoColor)), this, SLOT(updateFromWidgets(KoColor))); layout->addWidget(bar); m_d->widgetlist.append(bar); } else if (m_d->currentCS->colorChannelCount() == 3) { QRect newrect(0,0, this->geometry().width(), this->geometry().height()); KisVisualColorSelectorShape::ColorModel modelS = KisVisualColorSelectorShape::HSV; int channel1 = 0; int channel2 = 1; int channel3 = 2; switch(m_d->acs_config.subTypeParameter) { case KisColorSelectorConfiguration::H: channel1 = 0; break; case KisColorSelectorConfiguration::hsyS: case KisColorSelectorConfiguration::hsiS: case KisColorSelectorConfiguration::hslS: case KisColorSelectorConfiguration::hsvS: channel1 = 1; break; case KisColorSelectorConfiguration::V: case KisColorSelectorConfiguration::L: case KisColorSelectorConfiguration::I: case KisColorSelectorConfiguration::Y: channel1 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter"); } switch(m_d->acs_config.mainTypeParameter) { case KisColorSelectorConfiguration::hsySH: modelS = KisVisualColorSelectorShape::HSY; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsiSH: modelS = KisVisualColorSelectorShape::HSI; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hslSH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsvSH: modelS = KisVisualColorSelectorShape::HSV; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::YH: modelS = KisVisualColorSelectorShape::HSY; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::LH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::IH: modelS = KisVisualColorSelectorShape::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::VH: modelS = KisVisualColorSelectorShape::HSV; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::SY: modelS = KisVisualColorSelectorShape::HSY; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SI: modelS = KisVisualColorSelectorShape::HSI; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SL: modelS = KisVisualColorSelectorShape::HSL; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SV: case KisColorSelectorConfiguration::SV2: modelS = KisVisualColorSelectorShape::HSV; channel2 = 1; channel3 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter"); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { modelS = KisVisualColorSelectorShape::HSV; //Triangle only really works in HSV mode. } KisVisualColorSelectorShape *bar; if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, borderWidth,KisVisualEllipticalSelectorShape::border); bar->resize(sizeValue, sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, borderWidth); bar->setMaximumWidth(borderWidth); bar->setMinimumWidth(borderWidth); bar->setMinimumHeight(sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, modelS, m_d->currentCS, channel1, channel1, m_d->displayRenderer, borderWidth, KisVisualEllipticalSelectorShape::borderMirrored); bar->resize(sizeValue, sizeValue); } else { // Accessing bar below would crash since it's not initialized. // Hopefully this can never happen. warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape"; Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape"); return; } bar->setColor(m_d->currentcolor); m_d->widgetlist.append(bar); KisVisualColorSelectorShape *block; if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); block->setGeometry(bar->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); block->setGeometry(bar->getSpaceForSquare(newrect)); } else { block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional, modelS, m_d->currentCS, channel2, channel3, m_d->displayRenderer); block->setGeometry(bar->getSpaceForCircle(newrect)); } block->setColor(m_d->currentcolor); connect (bar, SIGNAL(sigNewColor(KoColor)), block, SLOT(setColorFromSibling(KoColor))); connect (block, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); connect (bar, SIGNAL(sigHSXchange()), SLOT(HSXwrangler())); connect (block, SIGNAL(sigHSXchange()), SLOT(HSXwrangler())); m_d->widgetlist.append(block); } else if (m_d->currentCS->colorChannelCount() == 4) { KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 0, 1); KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional,KisVisualColorSelectorShape::Channel, m_d->currentCS, 2, 3); block->setMaximumWidth(width()*0.5); block->setMaximumHeight(height()); block2->setMaximumWidth(width()*0.5); block2->setMaximumHeight(height()); block->setColor(m_d->currentcolor); block2->setColor(m_d->currentcolor); connect (block, SIGNAL(sigNewColor(KoColor)), block2, SLOT(setColorFromSibling(KoColor))); connect (block2, SIGNAL(sigNewColor(KoColor)), SLOT(updateFromWidgets(KoColor))); layout->addWidget(block); layout->addWidget(block2); m_d->widgetlist.append(block); m_d->widgetlist.append(block2); } Q_ASSERT(m_d->widgetlist.size() == 2); this->setLayout(layout); } void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { m_d->displayRenderer = displayRenderer; if (m_d->widgetlist.size()>0) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setDisplayRenderer(displayRenderer); } } } void KisVisualColorSelector::updateSelectorElements(QObject *source) { //first lock all elements from sending updates, then update all elements. Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->blockSignals(true); } Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { if (shape!=source) { if (m_d->updateSelf) { shape->setColorFromSibling(m_d->currentcolor); } else { shape->setColor(m_d->currentcolor); } } } Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->blockSignals(false); } } void KisVisualColorSelector::updateFromWidgets(KoColor c) { m_d->currentcolor = c; m_d->updateSelf = true; if (m_d->updateLonesome) { slotSetColor(c); Q_EMIT sigNewColor(c); } else { Q_EMIT sigNewColor(c); } } void KisVisualColorSelector::leaveEvent(QEvent *) { m_d->updateSelf = false; } void KisVisualColorSelector::resizeEvent(QResizeEvent *) { int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); QRect newrect(0,0, this->geometry().width(), this->geometry().height()); if (!m_d->currentCS) { slotsetColorSpace(m_d->currentcolor.colorSpace()); } if (m_d->currentCS->colorChannelCount()==3) { if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) { m_d->widgetlist.at(0)->setMaximumWidth(borderWidth); m_d->widgetlist.at(0)->setMinimumWidth(borderWidth); m_d->widgetlist.at(0)->setMinimumHeight(sizeValue); m_d->widgetlist.at(0)->setMaximumHeight(sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } m_d->widgetlist.at(0)->setBorderWidth(borderWidth); if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect)); } } Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->update(); } } void KisVisualColorSelector::HSXwrangler() { //qDebug() << this << "HSXWrangler"; QVector currentCoordinates = QVector(3); QVector w1 = m_d->widgetlist.at(0)->getHSX(currentCoordinates, true); QVector w2 = m_d->widgetlist.at(1)->getHSX(currentCoordinates, true); QVector ch(3); ch[0] = m_d->widgetlist.at(0)->getChannels().at(0); ch[1] = m_d->widgetlist.at(1)->getChannels().at(0); ch[2] = m_d->widgetlist.at(1)->getChannels().at(1); currentCoordinates[ch[0]] = w1[ch[0]]; currentCoordinates[ch[1]] = w2[ch[1]]; currentCoordinates[ch[2]] = w2[ch[2]]; m_d->widgetlist.at(0)->setHSX(currentCoordinates, true); m_d->widgetlist.at(1)->setHSX(currentCoordinates, true); } diff --git a/libs/ui/widgets/KisVisualColorSelector.h b/libs/ui/widgets/KisVisualColorSelector.h index 2eabe09d33..1c4c881d3a 100644 --- a/libs/ui/widgets/KisVisualColorSelector.h +++ b/libs/ui/widgets/KisVisualColorSelector.h @@ -1,86 +1,86 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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_VISUAL_COLOR_SELECTOR_H #define KIS_VISUAL_COLOR_SELECTOR_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" - +#include "KisColorSelectorInterface.h" #include "kritaui_export.h" /** * @brief The KisVisualColorSelector class * * This gives a color selector box that draws gradients and everything. * * Unlike other color selectors, this one draws the full gamut of the given * colorspace. */ -class KRITAUI_EXPORT KisVisualColorSelector : public QWidget +class KRITAUI_EXPORT KisVisualColorSelector : public KisColorSelectorInterface { Q_OBJECT public: explicit KisVisualColorSelector(QWidget *parent = 0); ~KisVisualColorSelector(); /** * @brief setConfig * @param forceCircular * Force circular is for space where you only have room for a circular selector. * @param forceSelfUpdate * force self-update is for making it update itself when using a modal dialog. */ void setConfig(bool forceCircular, bool forceSelfUpdate); - KoColor getCurrentColor(); - -Q_SIGNALS: - void sigNewColor(KoColor c); + KoColor getCurrentColor() const; public Q_SLOTS: - void slotSetColor(KoColor c); + void slotSetColor(const KoColor &c); void slotsetColorSpace(const KoColorSpace *cs); void slotRebuildSelectors(); void configurationChanged(); void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer); + private Q_SLOTS: void updateFromWidgets(KoColor c); void HSXwrangler(); + protected: void leaveEvent(QEvent *); void resizeEvent(QResizeEvent *); + private: struct Private; const QScopedPointer m_d; void updateSelectorElements(QObject *source); void drawGradients(); }; #endif diff --git a/libs/ui/widgets/kis_custom_image_widget.cc b/libs/ui/widgets/kis_custom_image_widget.cc index 19ebb485a9..0d8c38a94e 100644 --- a/libs/ui/widgets/kis_custom_image_widget.cc +++ b/libs/ui/widgets/kis_custom_image_widget.cc @@ -1,465 +1,465 @@ /* This file is part of the Calligra project * Copyright (C) 2005 Thomas Zander * Copyright (C) 2005 C. Boemann * Copyright (C) 2007 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "widgets/kis_custom_image_widget.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 "kis_config.h" #include "KisPart.h" #include "kis_clipboard.h" #include "KisDocument.h" #include "widgets/kis_cmb_idlist.h" #include "widgets/squeezedcombobox.h" KisCustomImageWidget::KisCustomImageWidget(QWidget* parent, qint32 defWidth, qint32 defHeight, double resolution, const QString& defColorModel, const QString& defColorDepth, const QString& defColorProfile, const QString& imageName) : WdgNewImage(parent) { setObjectName("KisCustomImageWidget"); m_openPane = qobject_cast(parent); Q_ASSERT(m_openPane); txtName->setText(imageName); m_widthUnit = KoUnit(KoUnit::Pixel, resolution); doubleWidth->setValue(defWidth); doubleWidth->setDecimals(0); m_width = m_widthUnit.fromUserValue(defWidth); cmbWidthUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); cmbWidthUnit->setCurrentIndex(m_widthUnit.indexInListForUi(KoUnit::ListAll)); m_heightUnit = KoUnit(KoUnit::Pixel, resolution); doubleHeight->setValue(defHeight); doubleHeight->setDecimals(0); m_height = m_heightUnit.fromUserValue(defHeight); cmbHeightUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); cmbHeightUnit->setCurrentIndex(m_heightUnit.indexInListForUi(KoUnit::ListAll)); doubleResolution->setValue(72.0 * resolution); doubleResolution->setDecimals(0); imageGroupSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed); grpClipboard->hide(); sliderOpacity->setRange(0, 100, 0); sliderOpacity->setValue(100); sliderOpacity->setSuffix("%"); connect(cmbPredefined, SIGNAL(activated(int)), SLOT(predefinedClicked(int))); connect(doubleResolution, SIGNAL(valueChanged(double)), this, SLOT(resolutionChanged(double))); connect(cmbWidthUnit, SIGNAL(activated(int)), this, SLOT(widthUnitChanged(int))); connect(doubleWidth, SIGNAL(valueChanged(double)), this, SLOT(widthChanged(double))); connect(cmbHeightUnit, SIGNAL(activated(int)), this, SLOT(heightUnitChanged(int))); connect(doubleHeight, SIGNAL(valueChanged(double)), this, SLOT(heightChanged(double))); connect(createButton, SIGNAL(clicked()), this, SLOT(createImage())); createButton->setDefault(true); bnPortrait->setIcon(KisIconUtils::loadIcon("portrait")); connect(bnPortrait, SIGNAL(clicked()), SLOT(setPortrait())); connect(bnLandscape, SIGNAL(clicked()), SLOT(setLandscape())); bnLandscape->setIcon(KisIconUtils::loadIcon("landscape")); connect(doubleWidth, SIGNAL(valueChanged(double)), this, SLOT(switchPortraitLandscape())); connect(doubleHeight, SIGNAL(valueChanged(double)), this, SLOT(switchPortraitLandscape())); connect(bnSaveAsPredefined, SIGNAL(clicked()), this, SLOT(saveAsPredefined())); colorSpaceSelector->setCurrentColorModel(KoID(defColorModel)); colorSpaceSelector->setCurrentColorDepth(KoID(defColorDepth)); colorSpaceSelector->setCurrentProfile(defColorProfile); //connect(chkFromClipboard,SIGNAL(stateChanged(int)),this,SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(selectionChanged()), this, SLOT(clipboardDataChanged())); connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)), this, SLOT(clipboardDataChanged())); connect(colorSpaceSelector, SIGNAL(selectionChanged(bool)), createButton, SLOT(setEnabled(bool))); KisConfig cfg; intNumLayers->setValue(cfg.numDefaultLayers()); KoColor bcol(KoColorSpaceRegistry::instance()->rgb8()); bcol.fromQColor(cfg.defaultBackgroundColor()); cmbColor->setColor(bcol); setBackgroundOpacity(cfg.defaultBackgroundOpacity()); KisConfig::BackgroundStyle bgStyle = cfg.defaultBackgroundStyle(); if (bgStyle == KisConfig::LAYER) { radioBackgroundAsLayer->setChecked(true); } else { radioBackgroundAsProjection->setChecked(true); } fillPredefined(); switchPortraitLandscape(); // this makes the portrait and landscape buttons more // obvious what is selected by changing the higlight color QPalette p = QApplication::palette(); QPalette palette_highlight(p ); QColor c = p.color(QPalette::Highlight); palette_highlight.setColor(QPalette::Button, c); bnLandscape->setPalette(palette_highlight); bnPortrait->setPalette(palette_highlight); } void KisCustomImageWidget::showEvent(QShowEvent *) { fillPredefined(); this->createButton->setFocus(); this->createButton->setEnabled(true); } KisCustomImageWidget::~KisCustomImageWidget() { m_predefined.clear(); } void KisCustomImageWidget::resolutionChanged(double res) { if (m_widthUnit.type() == KoUnit::Pixel) { m_widthUnit.setFactor(res / 72.0); m_width = m_widthUnit.fromUserValue(doubleWidth->value()); } if (m_heightUnit.type() == KoUnit::Pixel) { m_heightUnit.setFactor(res / 72.0); m_height = m_heightUnit.fromUserValue(doubleHeight->value()); } } void KisCustomImageWidget::widthUnitChanged(int index) { doubleWidth->blockSignals(true); m_widthUnit = KoUnit::fromListForUi(index, KoUnit::ListAll); if (m_widthUnit.type() == KoUnit::Pixel) { doubleWidth->setDecimals(0); m_widthUnit.setFactor(doubleResolution->value() / 72.0); } else { doubleWidth->setDecimals(2); } doubleWidth->setValue(KoUnit::ptToUnit(m_width, m_widthUnit)); doubleWidth->blockSignals(false); } void KisCustomImageWidget::widthChanged(double value) { m_width = m_widthUnit.fromUserValue(value); } void KisCustomImageWidget::heightUnitChanged(int index) { doubleHeight->blockSignals(true); m_heightUnit = KoUnit::fromListForUi(index, KoUnit::ListAll); if (m_heightUnit.type() == KoUnit::Pixel) { doubleHeight->setDecimals(0); m_heightUnit.setFactor(doubleResolution->value() / 72.0); } else { doubleHeight->setDecimals(2); } doubleHeight->setValue(KoUnit::ptToUnit(m_height, m_heightUnit)); doubleHeight->blockSignals(false); } void KisCustomImageWidget::heightChanged(double value) { m_height = m_heightUnit.fromUserValue(value); } void KisCustomImageWidget::createImage() { createButton->setEnabled(false); KisDocument *doc = createNewImage(); if (doc) { doc->setModified(false); emit m_openPane->documentSelected(doc); } } KisDocument* KisCustomImageWidget::createNewImage() { const KoColorSpace * cs = colorSpaceSelector->currentColorSpace(); if (cs->colorModelId() == RGBAColorModelID && cs->colorDepthId() == Integer8BitsColorDepthID) { const KoColorProfile *profile = cs->profile(); if (profile->name().contains("linear") || profile->name().contains("scRGB") || profile->info().contains("linear") || profile->info().contains("scRGB")) { int result = QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Linear gamma RGB color spaces are not supposed to be used " "in 8-bit integer modes. It is suggested to use 16-bit integer " "or any floating point colorspace for linear profiles.\n\n" "Press \"Continue\" to create a 8-bit integer linear RGB color space " "or \"Cancel\" to return to the settings dialog."), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel); if (result == QMessageBox::Cancel) { dbgKrita << "Model RGB8" << "NOT SUPPORTED"; dbgKrita << ppVar(cs->name()); dbgKrita << ppVar(cs->profile()->name()); dbgKrita << ppVar(cs->profile()->info()); return 0; } } } KisDocument *doc = static_cast(KisPart::instance()->createDocument()); qint32 width, height; double resolution; resolution = doubleResolution->value() / 72.0; // internal resolution is in pixels per pt width = static_cast(0.5 + KoUnit::ptToUnit(m_width, KoUnit(KoUnit::Pixel, resolution))); height = static_cast(0.5 + KoUnit::ptToUnit(m_height, KoUnit(KoUnit::Pixel, resolution))); QColor qc = cmbColor->color().toQColor(); qc.setAlpha(backgroundOpacity()); KoColor bgColor(qc, cs); bool backgroundAsLayer = radioBackgroundAsLayer->isChecked(); doc->newImage(txtName->text(), width, height, cs, bgColor, backgroundAsLayer, intNumLayers->value(), txtDescription->toPlainText(), resolution); KisConfig cfg; cfg.setNumDefaultLayers(intNumLayers->value()); cfg.setDefaultBackgroundOpacity(backgroundOpacity()); cfg.setDefaultBackgroundColor(cmbColor->color().toQColor()); cfg.setDefaultBackgroundStyle(backgroundAsLayer ? KisConfig::LAYER : KisConfig::PROJECTION); return doc; } void KisCustomImageWidget::setNumberOfLayers(int layers) { intNumLayers->setValue(layers); } quint8 KisCustomImageWidget::backgroundOpacity() const { qint32 opacity = sliderOpacity->value(); if (!opacity) return 0; return (opacity * 255) / 100; } void KisCustomImageWidget::setBackgroundOpacity(quint8 value) { sliderOpacity->setValue((value * 100) / 255); } void KisCustomImageWidget::clipboardDataChanged() { } void KisCustomImageWidget::fillPredefined() { cmbPredefined->clear(); m_predefined.clear(); cmbPredefined->addItem(""); QStringList definitions = KoResourcePaths::findAllResources("data", "predefined_image_sizes/*.predefinedimage", KoResourcePaths::Recursive); definitions.sort(); if (!definitions.empty()) { Q_FOREACH (const QString &definition, definitions) { QFile f(definition); f.open(QIODevice::ReadOnly); if (f.exists()) { QString xml = QString::fromUtf8(f.readAll()); KisPropertiesConfigurationSP predefined = new KisPropertiesConfiguration; predefined->fromXML(xml); if (predefined->hasProperty("name") && predefined->hasProperty("width") && predefined->hasProperty("height") && predefined->hasProperty("resolution") && predefined->hasProperty("x-unit") && predefined->hasProperty("y-unit")) { m_predefined << predefined; cmbPredefined->addItem(predefined->getString("name")); } } } } cmbPredefined->setCurrentIndex(0); } void KisCustomImageWidget::predefinedClicked(int index) { if (index < 1 || index > m_predefined.size()) return; KisPropertiesConfigurationSP predefined = m_predefined[index - 1]; txtPredefinedName->setText(predefined->getString("name")); doubleResolution->setValue(predefined->getDouble("resolution")); cmbWidthUnit->setCurrentIndex(predefined->getInt("x-unit")); cmbHeightUnit->setCurrentIndex(predefined->getInt("y-unit")); widthUnitChanged(cmbWidthUnit->currentIndex()); heightUnitChanged(cmbHeightUnit->currentIndex()); doubleWidth->setValue(predefined->getDouble("width")); doubleHeight->setValue(predefined->getDouble("height")); } void KisCustomImageWidget::saveAsPredefined() { QString fileName = txtPredefinedName->text(); if (fileName.isEmpty()) { return; } QString saveLocation = KoResourcePaths::saveLocation("data", "predefined_image_sizes/", true); - QFile f(saveLocation + '/' + fileName.replace(' ', '_').replace('(', '_').replace(')', '_') + ".predefinedimage"); + QFile f(saveLocation + '/' + fileName.replace(' ', '_').replace('(', '_').replace(')', '_').replace(':', '_') + ".predefinedimage"); f.open(QIODevice::WriteOnly | QIODevice::Truncate); KisPropertiesConfigurationSP predefined = new KisPropertiesConfiguration(); predefined->setProperty("name", txtPredefinedName->text()); predefined->setProperty("width", doubleWidth->value()); predefined->setProperty("height", doubleHeight->value()); predefined->setProperty("resolution", doubleResolution->value()); predefined->setProperty("x-unit", cmbWidthUnit->currentIndex()); predefined->setProperty("y-unit", cmbHeightUnit->currentIndex()); QString xml = predefined->toXML(); f.write(xml.toUtf8()); f.flush(); f.close(); int i = 0; bool found = false; Q_FOREACH (KisPropertiesConfigurationSP pr, m_predefined) { if (pr->getString("name") == txtPredefinedName->text()) { found = true; break; } ++i; } if (found) { m_predefined[i] = predefined; } else { m_predefined.append(predefined); cmbPredefined->addItem(txtPredefinedName->text()); } } void KisCustomImageWidget::setLandscape() { if (doubleWidth->value() < doubleHeight->value()) { switchWidthHeight(); } } void KisCustomImageWidget::setPortrait() { if (doubleWidth->value() > doubleHeight->value()) { switchWidthHeight(); } } void KisCustomImageWidget::switchWidthHeight() { double width = doubleWidth->value(); double height = doubleHeight->value(); doubleHeight->blockSignals(true); doubleWidth->blockSignals(true); cmbWidthUnit->blockSignals(true); cmbHeightUnit->blockSignals(true); doubleWidth->setValue(height); doubleHeight->setValue(width); cmbWidthUnit->setCurrentIndex(m_heightUnit.indexInListForUi(KoUnit::ListAll)); cmbHeightUnit->setCurrentIndex(m_widthUnit.indexInListForUi(KoUnit::ListAll)); doubleHeight->blockSignals(false); doubleWidth->blockSignals(false); cmbWidthUnit->blockSignals(false); cmbHeightUnit->blockSignals(false); switchPortraitLandscape(); widthChanged(doubleWidth->value()); heightChanged(doubleHeight->value()); } void KisCustomImageWidget::switchPortraitLandscape() { if(doubleWidth->value() > doubleHeight->value()) bnLandscape->setChecked(true); else bnPortrait->setChecked(true); } diff --git a/libs/ui/widgets/kis_paintop_presets_chooser_popup.cpp b/libs/ui/widgets/kis_paintop_presets_chooser_popup.cpp index 9cc72189e1..e791f85e5a 100644 --- a/libs/ui/widgets/kis_paintop_presets_chooser_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_chooser_popup.cpp @@ -1,126 +1,156 @@ /* This file is part of the KDE project * Copyright (c) 2010 Sven Langkamp * Copyright 2011 Srikanth Tiyyagura * * 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_paintop_presets_chooser_popup.h" #include #include #include +#include #include #include #include #include #include #include #include #include struct KisPaintOpPresetsChooserPopup::Private { public: Ui_WdgPaintOpPresets uiWdgPaintOpPresets; bool firstShown; }; KisPaintOpPresetsChooserPopup::KisPaintOpPresetsChooserPopup(QWidget * parent) : QWidget(parent) , m_d(new Private()) { m_d->uiWdgPaintOpPresets.setupUi(this); - QMenu* menu = new QMenu(this); + QMenu* menu = new QMenu(this); + menu->setStyleSheet("margin: 6px"); + + menu->addSection(i18n("Display")); QActionGroup *actionGroup = new QActionGroup(this); KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), this, SLOT(slotThumbnailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::THUMBNAIL); action->setActionGroup(actionGroup); action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), this, SLOT(slotDetailMode())); action->setCheckable(true); action->setChecked(mode == KisPresetChooser::DETAIL); action->setActionGroup(actionGroup); + // add widget slider to control icon size + QSlider* iconSizeSlider = new QSlider(this); + iconSizeSlider->setOrientation(Qt::Horizontal); + iconSizeSlider->setRange(30, 80); + iconSizeSlider->setValue(m_d->uiWdgPaintOpPresets.wdgPresetChooser->iconSize()); + iconSizeSlider->setMinimumHeight(20); + iconSizeSlider->setMinimumWidth(40); + iconSizeSlider->setTickInterval(10); + + + QWidgetAction *sliderAction= new QWidgetAction(this); + sliderAction->setDefaultWidget(iconSizeSlider); + + menu->addSection(i18n("Icon Size")); + menu->addAction(sliderAction); + + + + // setting the view mode m_d->uiWdgPaintOpPresets.wdgPresetChooser->setViewMode(mode); m_d->uiWdgPaintOpPresets.wdgPresetChooser->showTaggingBar(true); m_d->uiWdgPaintOpPresets.wdgPresetChooser->itemChooser()->setViewModeButtonVisible(true); QToolButton *viewModeButton = m_d->uiWdgPaintOpPresets.wdgPresetChooser->itemChooser()->viewModeButton(); viewModeButton->setMenu(menu); connect(m_d->uiWdgPaintOpPresets.wdgPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(resourceSelected(KoResource*))); connect(m_d->uiWdgPaintOpPresets.wdgPresetChooser, SIGNAL(resourceClicked(KoResource*)), - this, SIGNAL(resourceClicked(KoResource*))); - + this, SIGNAL(resourceClicked(KoResource*))) ; + + + connect (iconSizeSlider, SIGNAL(sliderMoved(int)), + m_d->uiWdgPaintOpPresets.wdgPresetChooser, SLOT(setIconSize(int))); + connect( iconSizeSlider, SIGNAL(sliderReleased()), + m_d->uiWdgPaintOpPresets.wdgPresetChooser, SLOT(saveIconSize())); + + m_d->firstShown = true; } KisPaintOpPresetsChooserPopup::~KisPaintOpPresetsChooserPopup() { delete m_d; } void KisPaintOpPresetsChooserPopup::slotThumbnailMode() { KisConfig().setPresetChooserViewMode(KisPresetChooser::THUMBNAIL); m_d->uiWdgPaintOpPresets.wdgPresetChooser->setViewMode(KisPresetChooser::THUMBNAIL); } void KisPaintOpPresetsChooserPopup::slotDetailMode() { KisConfig().setPresetChooserViewMode(KisPresetChooser::DETAIL); m_d->uiWdgPaintOpPresets.wdgPresetChooser->setViewMode(KisPresetChooser::DETAIL); } void KisPaintOpPresetsChooserPopup::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); //Workaround to get the column and row size right if(m_d->firstShown) { m_d->uiWdgPaintOpPresets.wdgPresetChooser->updateViewSettings(); m_d->firstShown = false; } } void KisPaintOpPresetsChooserPopup::showButtons(bool show) { m_d->uiWdgPaintOpPresets.wdgPresetChooser->showButtons(show); } void KisPaintOpPresetsChooserPopup::canvasResourceChanged(KisPaintOpPresetSP preset) { if (preset) { blockSignals(true); m_d->uiWdgPaintOpPresets.wdgPresetChooser->setCurrentResource(preset.data()); blockSignals(false); } m_d->uiWdgPaintOpPresets.wdgPresetChooser->updateViewSettings(); } void KisPaintOpPresetsChooserPopup::updateViewSettings() { m_d->uiWdgPaintOpPresets.wdgPresetChooser->updateViewSettings(); } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.cpp b/libs/ui/widgets/kis_paintop_presets_popup.cpp index f92e47ab48..7438a4a98c 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.cpp +++ b/libs/ui/widgets/kis_paintop_presets_popup.cpp @@ -1,440 +1,601 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * 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 "widgets/kis_paintop_presets_popup.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 "kis_config.h" #include "kis_resource_server_provider.h" #include "kis_lod_availability_widget.h" #include "kis_signal_auto_connection.h" +// ones from brush engine selector +#include +#include "../kis_paint_ops_model.h" + + + struct KisPaintOpPresetsPopup::Private { public: Ui_WdgPaintOpSettings uiWdgPaintOpPresetSettings; QGridLayout *layout; KisPaintOpConfigWidget *settingsWidget; QFont smallFont; KisCanvasResourceProvider *resourceProvider; bool detached; bool ignoreHideEvents; QSize minimumSettingsWidgetSize; QRect detachedGeometry; KisSignalAutoConnectionsStore widgetConnections; }; KisPaintOpPresetsPopup::KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent) : QWidget(parent) , m_d(new Private()) { setObjectName("KisPaintOpPresetsPopup"); setFont(KoDockRegistry::dockFont()); + current_paintOpId = ""; + m_d->resourceProvider = resourceProvider; m_d->uiWdgPaintOpPresetSettings.setupUi(this); m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer); m_d->layout->setSizeConstraint(QLayout::SetFixedSize); m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white); m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200)); m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); + + // DETAIL and THUMBNAIL view changer + QMenu* menu = new QMenu(this); + + menu->setStyleSheet("margin: 6px"); + menu->addSection(i18n("Display")); + + QActionGroup *actionGroup = new QActionGroup(this); + + KisPresetChooser::ViewMode mode = (KisPresetChooser::ViewMode)KisConfig().presetChooserViewMode(); + + QAction* action = menu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotThumbnailMode())); + action->setCheckable(true); + action->setChecked(mode == KisPresetChooser::THUMBNAIL); + action->setActionGroup(actionGroup); + + action = menu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"), m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotDetailMode())); + action->setCheckable(true); + action->setChecked(mode == KisPresetChooser::DETAIL); + action->setActionGroup(actionGroup); + + + // add horizontal slider for the icon size + QSlider* iconSizeSlider = new QSlider(this); + iconSizeSlider->setOrientation(Qt::Horizontal); + iconSizeSlider->setRange(30, 80); + iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize()); + iconSizeSlider->setMinimumHeight(20); + iconSizeSlider->setMinimumWidth(40); + iconSizeSlider->setTickInterval(10); + + + QWidgetAction *sliderAction= new QWidgetAction(this); + sliderAction->setDefaultWidget(iconSizeSlider); + + menu->addSection(i18n("Icon Size")); + menu->addAction(sliderAction); + + + + + // configure the button and assign menu + m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setMenu(menu); + m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); + m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setPopupMode(QToolButton::InstantPopup); + + + // show/hide buttons + + KisConfig cfg; + + m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true); + m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible()); + m_d->uiWdgPaintOpPresetSettings.showEditorButton->setCheckable(true); + m_d->uiWdgPaintOpPresetSettings.showEditorButton->setChecked(true); + + m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setText(i18n("Presets")); + m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true); + m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(false); // use a config to load/save this state + slotSwitchShowPresets(false); // hide presets by default + + + // Connections + + + connect(iconSizeSlider, SIGNAL(sliderMoved(int)), + m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSetIconSize(int))); + + connect(iconSizeSlider, SIGNAL(sliderReleased()), + m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(slotSaveIconSize())); + + + connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)), + this, SLOT(slotSwitchScratchpad(bool))); + + connect(m_d->uiWdgPaintOpPresetSettings.showEditorButton, SIGNAL(clicked(bool)), + this, SLOT(slotSwitchShowEditor(bool))); + + connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool))); + connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault())); connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillLayer())); connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient())); connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground())); connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage())); m_d->settingsWidget = 0; setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), this, SIGNAL(savePresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), this, SIGNAL(reloadPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), this, SIGNAL(defaultPresetClicked())); connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(dirtyPresetToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushSizeToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)), this, SIGNAL(eraserBrushOpacityToggled(bool))); connect(m_d->uiWdgPaintOpPresetSettings.bnDefaultPreset, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.txtPreset, SLOT(clear())); connect(m_d->uiWdgPaintOpPresetSettings.txtPreset, SIGNAL(textChanged(QString)), SLOT(slotWatchPresetNameLineEdit())); - connect(m_d->uiWdgPaintOpPresetSettings.paintopList, SIGNAL(activated(QString)), + connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(activated(QString)), this, SIGNAL(paintopActivated(QString))); + + // preset widget connections connect(m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(signalResourceSelected(KoResource*))); connect(m_d->uiWdgPaintOpPresetSettings.bnSave, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); connect(m_d->uiWdgPaintOpPresetSettings.reload, SIGNAL(clicked()), m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser, SLOT(updateViewSettings())); - KisConfig cfg; - m_d->detached = !cfg.paintopPopupDetached(); + + + m_d->detached = false; m_d->ignoreHideEvents = false; m_d->minimumSettingsWidgetSize = QSize(0, 0); - m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(cfg.presetStripVisible()); m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible()); m_d->detachedGeometry = QRect(100, 100, 0, 0); m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets()); m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize()); m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity()); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setCanvasResourceManager(resourceProvider->resourceManager()); + // brush engine is changed + connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPaintOpChanged(int))); + connect(resourceProvider->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), SLOT(slotResourceChanged(int, QVariant))); connect(m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability, SIGNAL(sigUserChangedLodAvailability(bool)), SLOT(slotLodAvailabilityChanged(bool))); slotResourceChanged(KisCanvasResourceProvider::LodAvailability, resourceProvider->resourceManager()-> resource(KisCanvasResourceProvider::LodAvailability)); + + + connect(m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPaintOpChanged(int))); + } + void KisPaintOpPresetsPopup::slotResourceChanged(int key, const QVariant &value) { if (key == KisCanvasResourceProvider::LodAvailability) { m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->slotUserChangedLodAvailability(value.toBool()); } } void KisPaintOpPresetsPopup::slotLodAvailabilityChanged(bool value) { m_d->resourceProvider->resourceManager()->setResource(KisCanvasResourceProvider::LodAvailability, QVariant(value)); } KisPaintOpPresetsPopup::~KisPaintOpPresetsPopup() { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->settingsWidget->hide(); m_d->settingsWidget->setParent(0); m_d->settingsWidget = 0; } delete m_d; } void KisPaintOpPresetsPopup::setPaintOpSettingsWidget(QWidget * widget) { if (m_d->settingsWidget) { m_d->layout->removeWidget(m_d->settingsWidget); m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry(); } m_d->layout->update(); updateGeometry(); m_d->widgetConnections.clear(); m_d->settingsWidget = 0; if (widget) { m_d->settingsWidget = dynamic_cast(widget); KIS_ASSERT_RECOVER_RETURN(m_d->settingsWidget); if (m_d->settingsWidget->supportScratchBox()) { showScratchPad(); } else { hideScratchPad(); } m_d->widgetConnections.addConnection(m_d->settingsWidget, SIGNAL(sigConfigurationItemChanged()), this, SLOT(slotUpdateLodAvailability())); widget->setFont(m_d->smallFont); QSize hint = widget->sizeHint(); m_d->minimumSettingsWidgetSize = QSize(qMax(hint.width(), m_d->minimumSettingsWidgetSize.width()), qMax(hint.height(), m_d->minimumSettingsWidgetSize.height())); widget->setMinimumSize(m_d->minimumSettingsWidgetSize); m_d->layout->addWidget(widget); m_d->layout->update(); widget->show(); } slotUpdateLodAvailability(); } void KisPaintOpPresetsPopup::slotUpdateLodAvailability() { if (!m_d->settingsWidget) return; KisPaintopLodLimitations l = m_d->settingsWidget->lodLimitations(); m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLimitations(l); } void KisPaintOpPresetsPopup::slotWatchPresetNameLineEdit() { QString text = m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); KisPaintOpPresetResourceServer * rServer = KisResourceServerProvider::instance()->paintOpPresetServer(); bool overwrite = rServer->resourceByName(text) != 0; KisPaintOpPresetSP preset = m_d->resourceProvider->currentPreset(); bool btnSaveAvailable = preset->valid() && (preset->isPresetDirty() | !overwrite); QString btnText = overwrite ? i18n("Overwrite Preset") : i18n("Save to Presets"); m_d->uiWdgPaintOpPresetSettings.bnSave->setText(btnText); m_d->uiWdgPaintOpPresetSettings.bnSave->setEnabled(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.reload->setVisible(true); m_d->uiWdgPaintOpPresetSettings.reload->setEnabled(btnSaveAvailable && overwrite); QFont font = m_d->uiWdgPaintOpPresetSettings.txtPreset->font(); font.setItalic(btnSaveAvailable); m_d->uiWdgPaintOpPresetSettings.txtPreset->setFont(font); } QString KisPaintOpPresetsPopup::getPresetName() const { return m_d->uiWdgPaintOpPresetSettings.txtPreset->text(); } QImage KisPaintOpPresetsPopup::cutOutOverlay() { return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay(); } void KisPaintOpPresetsPopup::contextMenuEvent(QContextMenuEvent *e) { Q_UNUSED(e); - -#if 0 - QMenu menu(this); - QAction* action = menu.addAction(m_d->detached ? i18n("Attach to Toolbar") : i18n("Detach from Toolbar")); - connect(action, SIGNAL(triggered()), this, SLOT(switchDetached())); - QAction* showPresetStrip = menu.addAction(i18n("Show Preset Strip")); - showPresetStrip->setCheckable(true); - showPresetStrip->setChecked(m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()); - connect(showPresetStrip, SIGNAL(triggered(bool)), this, SLOT(slotSwitchPresetStrip(bool))); - QAction* showScratchPad = menu.addAction(i18n("Show Scratchpad")); - showScratchPad->setCheckable(true); - showScratchPad->setChecked(m_d->uiWdgPaintOpPresetSettings.scratchPad->isVisible()); - connect(showScratchPad, SIGNAL(triggered(bool)), this, SLOT(slotSwitchScratchpad(bool))); - menu.exec(e->globalPos()); -#endif } void KisPaintOpPresetsPopup::switchDetached(bool show) { if (parentWidget()) { m_d->detached = !m_d->detached; if (m_d->detached) { m_d->ignoreHideEvents = true; - parentWidget()->setWindowFlags(Qt::Tool); - m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(false); + if (show) { parentWidget()->show(); } m_d->ignoreHideEvents = false; + } else { - parentWidget()->setWindowFlags(Qt::Popup); KisConfig cfg; - m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(cfg.scratchpadVisible()); parentWidget()->hide(); } KisConfig cfg; cfg.setPaintopPopupDetached(m_d->detached); } } void KisPaintOpPresetsPopup::hideScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(false); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(false); } void KisPaintOpPresetsPopup::showScratchPad() { m_d->uiWdgPaintOpPresetSettings.scratchPad->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillGradient->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.fillSolid->setEnabled(true); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setEnabled(true); } void KisPaintOpPresetsPopup::resourceSelected(KoResource* resource) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(resource); m_d->uiWdgPaintOpPresetSettings.txtPreset->setText(resource->name()); slotWatchPresetNameLineEdit(); + m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(resource->name()); +} + +bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2) +{ + return v1.priority < v2.priority; } void KisPaintOpPresetsPopup::setPaintOpList(const QList< KisPaintOpFactory* >& list) { - m_d->uiWdgPaintOpPresetSettings.paintopList->setPaintOpList(list); + m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->clear(); // reset combobox list just in case + + // create a new list so we can sort it and populate the brush engine combo box + QList sortedList; + for(int i=0; i < list.length(); i++) { + + QString fileName = KoResourcePaths::findResource("kis_images", list.at(i)->pixmap()); + QPixmap pixmap(fileName); + + if(pixmap.isNull()){ + pixmap = QPixmap(22,22); + pixmap.fill(); + } + + KisPaintOpInfo paintOpInfo; + paintOpInfo.id = list.at(i)->id(); + paintOpInfo.name = list.at(i)->name(); + paintOpInfo.icon = pixmap; + paintOpInfo.priority = list.at(i)->priority(); + + sortedList.append(paintOpInfo); + } + + qStableSort(sortedList.begin(), sortedList.end(), variantLessThan ); + + // add an "All" option at the front to show all presets + QPixmap emptyPixmap = QPixmap(22,22); + emptyPixmap.fill(palette().color(QPalette::Background)); + sortedList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), emptyPixmap, 0 )); + + // fill the list into the brush combo box + for (int m = 0; m < sortedList.length(); m++) { + m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->addItem(sortedList[m].icon, sortedList[m].name, QVariant(sortedList[m].id)); + } + + } + void KisPaintOpPresetsPopup::setCurrentPaintOp(const QString& paintOpId) { - m_d->uiWdgPaintOpPresetSettings.paintopList->setCurrent(paintOpId); - m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(paintOpId); + // iterate through the items and find the engine we need the combo box to switch to + for (int i= 0; i < m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->count(); i++) { + + QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->itemData(i); // grab paintOpID from data + QString currentPaintOpId = userData.toString(); + + if (paintOpId == currentPaintOpId) { + m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->setCurrentIndex(i); // found it! + } + } + + // if the "all" option is set, set the filter to "", that way it clears the filter and shows everything + QString paintOpFilter = paintOpId; + if (paintOpFilter == "all_options") { + paintOpFilter = ""; + } + + m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(paintOpFilter); + } QString KisPaintOpPresetsPopup::currentPaintOp() { - return m_d->uiWdgPaintOpPresetSettings.paintopList->currentItem(); + QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data + QString currentPaintOpId = userData.toString(); + return currentPaintOpId; +} + +QString KisPaintOpPresetsPopup::currentPaintOpId() { + return current_paintOpId; } void KisPaintOpPresetsPopup::setPresetImage(const QImage& image) { m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(image); } void KisPaintOpPresetsPopup::hideEvent(QHideEvent *event) { if (m_d->ignoreHideEvents) { return; } if (m_d->detached) { m_d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisPaintOpPresetsPopup::showEvent(QShowEvent *) { if (m_d->detached) { window()->setGeometry(m_d->detachedGeometry); } emit brushEditorShown(); } void KisPaintOpPresetsPopup::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); emit sizeChanged(); } bool KisPaintOpPresetsPopup::detached() const { return m_d->detached; } -void KisPaintOpPresetsPopup::slotSwitchPresetStrip(bool visible) -{ - m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible); - KisConfig cfg; - cfg.setPresetStripVisible(visible); -} - void KisPaintOpPresetsPopup::slotSwitchScratchpad(bool visible) { m_d->uiWdgPaintOpPresetSettings.scratchpadControls->setVisible(visible); KisConfig cfg; cfg.setScratchpadVisible(visible); } +void KisPaintOpPresetsPopup::slotSwitchShowEditor(bool visible) { + m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible); +} + +void KisPaintOpPresetsPopup::slotSwitchShowPresets(bool visible) { + m_d->uiWdgPaintOpPresetSettings.presetsContainer->setVisible(visible); +} + +void KisPaintOpPresetsPopup::slotPaintOpChanged(int index) { + + Q_UNUSED(index); + + QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEgineComboBox->currentData(); // grab paintOpID from data + QString currentPaintOpId = userData.toString(); + + setCurrentPaintOp(currentPaintOpId); + emit paintopActivated(currentPaintOpId); // tell the toolbar to change the active icon +} + + void KisPaintOpPresetsPopup::updateViewSettings() { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->updateViewSettings(); } void KisPaintOpPresetsPopup::currentPresetChanged(KisPaintOpPresetSP preset) { m_d->uiWdgPaintOpPresetSettings.presetWidget->smallPresetChooser->setCurrentResource(preset.data()); } void KisPaintOpPresetsPopup::updateThemedIcons() { m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new")); m_d->uiWdgPaintOpPresetSettings.fillLayer->hide(); m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient")); m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill")); m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete")); m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); + m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose")); } diff --git a/libs/ui/widgets/kis_paintop_presets_popup.h b/libs/ui/widgets/kis_paintop_presets_popup.h index 6751dbe54c..1d24db9ebd 100644 --- a/libs/ui/widgets/kis_paintop_presets_popup.h +++ b/libs/ui/widgets/kis_paintop_presets_popup.h @@ -1,117 +1,124 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2008 * Copyright (C) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * * 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_PAINTOP_PRESETS_POPUP_H #define KIS_PAINTOP_PRESETS_POPUP_H #include #include #include #include #include class QString; class KisCanvasResourceProvider; class KoResource; /** * Popup widget for presets with built-in functionality * for adding and removing presets. */ class KisPaintOpPresetsPopup : public QWidget { Q_OBJECT public: KisPaintOpPresetsPopup(KisCanvasResourceProvider * resourceProvider, QWidget * parent = 0); ~KisPaintOpPresetsPopup(); void setPaintOpSettingsWidget(QWidget * widget); /** * @return the name entered in the preset name lineedit */ QString getPresetName() const; ///Image for preset preview ///@return image cut out from the scratchpad QImage cutOutOverlay(); void setPaintOpList(const QList& list); void setCurrentPaintOp(const QString & paintOpId); QString currentPaintOp(); + + /// returns the internal ID for the paint op (brush engine) + QString currentPaintOpId(); ///fill the cutoutOverlay rect with the cotent of an image, used to get the image back when selecting a preset ///@param image image that will be used, should be image of an existing preset resource void setPresetImage(const QImage& image); virtual void resizeEvent(QResizeEvent* ); bool detached() const; void updateViewSettings(); void currentPresetChanged(KisPaintOpPresetSP preset); protected: void contextMenuEvent(QContextMenuEvent *); void hideEvent(QHideEvent *); void showEvent(QShowEvent *); public Q_SLOTS: void slotWatchPresetNameLineEdit(); void switchDetached(bool show = true); void hideScratchPad(); void showScratchPad(); void resourceSelected(KoResource* resource); void updateThemedIcons(); void slotUpdateLodAvailability(); Q_SIGNALS: void savePresetClicked(); void defaultPresetClicked(); void paintopActivated(const QString& presetName); void signalResourceSelected(KoResource* resource); void reloadPresetClicked(); void dirtyPresetToggled(bool value); void eraserBrushSizeToggled(bool value); void eraserBrushOpacityToggled(bool value); - void sizeChanged(); void brushEditorShown(); private Q_SLOTS: - void slotSwitchPresetStrip(bool visible); void slotSwitchScratchpad(bool visible); void slotResourceChanged(int key, const QVariant &value); void slotLodAvailabilityChanged(bool value); + void slotSwitchShowEditor(bool visible); + void slotPaintOpChanged(int index); + void slotSwitchShowPresets(bool visible); + + private: struct Private; Private * const m_d; + QString current_paintOpId; }; #endif diff --git a/libs/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp index aa56f74343..bb931df1c6 100644 --- a/libs/ui/widgets/kis_preset_chooser.cpp +++ b/libs/ui/widgets/kis_preset_chooser.cpp @@ -1,309 +1,342 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_preset_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceItemView.h" #include #include #include "kis_resource_server_provider.h" #include "kis_global.h" #include "kis_slider_spin_box.h" #include "kis_config.h" #include "kis_config_notifier.h" #include /// The resource item delegate for rendering the resource preview class KisPresetDelegate : public QAbstractItemDelegate { public: KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {} ~KisPresetDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } void setShowText(bool showText) { m_showText = showText; } void setUseDirtyPresets(bool value) { m_useDirtyPresets = value; } private: bool m_showText; bool m_useDirtyPresets; }; void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { painter->save(); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if (! index.isValid()) return; KisPaintOpPreset* preset = static_cast(index.internalPointer()); QImage preview = preset->image(); if(preview.isNull()) { return; } QRect paintRect = option.rect.adjusted(1, 1, -1, -1); if (!m_showText) { painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { QSize pixSize(paintRect.height(), paintRect.height()); painter->drawImage(paintRect.x(), paintRect.y(), preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, preset->name()); + // Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small + QString dirtyPresetIndicator = QString(""); + if (m_useDirtyPresets && preset->isPresetDirty()) { + dirtyPresetIndicator = QString("*"); + } + + painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator)); } if (m_useDirtyPresets && preset->isPresetDirty()) { const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset")); QPixmap pixmap = icon.pixmap(QSize(15,15)); painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap); } if (!preset->settings() || !preset->settings()->isValid()) { const QIcon icon = KisIconUtils::loadIcon("broken-preset"); icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25)); } if (option.state & QStyle::State_Selected) { painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(1.0); painter->fillRect(option.rect, option.palette.highlight()); // highlight is not strong enough to pick out preset. draw border around it. painter->setCompositionMode(QPainter::CompositionMode_SourceOver); painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin)); QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets painter->drawRect(selectedBorder); } painter->restore(); } class KisPresetProxyAdapter : public KisPaintOpPresetResourceServerAdapter { public: KisPresetProxyAdapter(KisPaintOpPresetResourceServer* resourceServer) : KisPaintOpPresetResourceServerAdapter(resourceServer) { setSortingEnabled(true); } ~KisPresetProxyAdapter() override {} QList< KoResource* > resources() override { QList serverResources = KisPaintOpPresetResourceServerAdapter::resources(); if (m_paintopID.isEmpty()) { return serverResources; } QList resources; Q_FOREACH (KoResource *resource, serverResources) { KisPaintOpPreset *preset = dynamic_cast(resource); if (preset && preset->paintOp().id() == m_paintopID) { resources.append(preset); } } return resources; } ///Set id for paintop to be accept by the proxy model, if not filter is set all ///presets will be shown. void setPresetFilter(const QString& paintOpId) { m_paintopID = paintOpId; invalidate(); } ///Resets the model connected to the adapter void invalidate() { emitRemovingResource(0); } QString currentPaintOpId() const { return m_paintopID; } private: QString m_paintopID; }; KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name) : QWidget(parent) { setObjectName(name); QVBoxLayout * layout = new QVBoxLayout(this); layout->setMargin(0); KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); m_adapter = QSharedPointer(new KisPresetProxyAdapter(rserver)); m_chooser = new KoResourceItemChooser(m_adapter, this); m_chooser->setColumnCount(10); m_chooser->setRowHeight(50); m_delegate = new KisPresetDelegate(this); m_chooser->setItemDelegate(m_delegate); m_chooser->setSynced(true); layout->addWidget(m_chooser); connect(m_chooser, SIGNAL(resourceSelected(KoResource*)), this, SIGNAL(resourceSelected(KoResource*))); connect(m_chooser, SIGNAL(resourceClicked(KoResource*)), this, SIGNAL(resourceClicked(KoResource*))); m_mode = THUMBNAIL; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(notifyConfigChanged())); + + notifyConfigChanged(); } KisPresetChooser::~KisPresetChooser() { } void KisPresetChooser::showButtons(bool show) { m_chooser->showButtons(show); } void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode) { m_mode = mode; updateViewSettings(); } void KisPresetChooser::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); updateViewSettings(); } void KisPresetChooser::notifyConfigChanged() { KisConfig cfg; m_delegate->setUseDirtyPresets(cfg.useDirtyPresets()); + setIconSize(cfg.presetIconSize() ); + updateViewSettings(); } void KisPresetChooser::updateViewSettings() { if (m_mode == THUMBNAIL) { m_chooser->setSynced(true); m_delegate->setShowText(false); } else if (m_mode == DETAIL) { m_chooser->setSynced(false); m_chooser->setColumnCount(1); + m_chooser->setColumnWidth(m_chooser->width()); + KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); m_chooser->setRowHeight(chooserSync->baseLength()); m_delegate->setShowText(true); } else if (m_mode == STRIP) { m_chooser->setSynced(false); m_chooser->setRowCount(1); m_chooser->itemView()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_chooser->itemView()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7); m_delegate->setShowText(false); } } void KisPresetChooser::setCurrentResource(KoResource *resource) { /** * HACK ALERT: here we use a direct call to an adapter to notify the view * that the preset might have changed its dirty state. This state * doesn't affect the filtering so the server's cache must not be * invalidated! * * Ideally, we should call some method of KoResourceServer instead, * but ut seems like a bit too much effort for such a small fix. */ if (resource == currentResource()) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); KisPaintOpPreset *preset = dynamic_cast(resource); if (preset) { adapter->resourceChangedNoCacheInvalidation(preset); } } m_chooser->setCurrentResource(resource); } KoResource* KisPresetChooser::currentResource() { return m_chooser->currentResource(); } void KisPresetChooser::showTaggingBar(bool show) { m_chooser->showTaggingBar(show); } KoResourceItemChooser *KisPresetChooser::itemChooser() { return m_chooser; } + void KisPresetChooser::setPresetFilter(const QString& paintOpId) { KisPresetProxyAdapter *adapter = static_cast(m_adapter.data()); if (adapter->currentPaintOpId() != paintOpId) { adapter->setPresetFilter(paintOpId); updateViewSettings(); } } +void KisPresetChooser::setIconSize(int newSize) +{ + KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); + chooserSync->setBaseLength(newSize); + updateViewSettings(); +} + +int KisPresetChooser::iconSize() +{ + KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance(); + + return chooserSync->baseLength(); +} + +void KisPresetChooser::saveIconSize() +{ + // save icon size + KisConfig cfg; + cfg.setPresetIconSize(iconSize()); +} diff --git a/libs/ui/widgets/kis_preset_chooser.h b/libs/ui/widgets/kis_preset_chooser.h index c32ad8abad..9215870cac 100644 --- a/libs/ui/widgets/kis_preset_chooser.h +++ b/libs/ui/widgets/kis_preset_chooser.h @@ -1,84 +1,93 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (C) 2011 Silvio Heinrich * 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. */ #ifndef KIS_ITEM_CHOOSER_H_ #define KIS_ITEM_CHOOSER_H_ #include #include #include class KoAbstractResourceServerAdapter; class KisPresetDelegate; class KoResourceItemChooser; class KoResource; /** * A special type of item chooser that can contain extra widgets that show * more information about the currently selected item. Reimplement update() * to extract that information and fill the appropriate widgets. */ class KRITAUI_EXPORT KisPresetChooser : public QWidget { Q_OBJECT public: KisPresetChooser(QWidget *parent = 0, const char *name = 0); virtual ~KisPresetChooser(); enum ViewMode{ THUMBNAIL, /// Shows thumbnails DETAIL, /// Shows thumbsnails with text next to it STRIP /// Shows thumbnails arranged in a single row }; /// Sets a list of resources in the paintop list, when ever user press enter in the linedit of paintop_presets_popup Class void setViewMode(ViewMode mode); void showButtons(bool show); void setCurrentResource(KoResource *resource); KoResource* currentResource(); /// Sets the visibility of tagging klineEdits void showTaggingBar(bool show); KoResourceItemChooser *itemChooser(); void setPresetFilter(const QString& paintOpId); + /// get the base size for the icons. Used by the slider in the view options + int iconSize(); + Q_SIGNALS: void resourceSelected(KoResource *resource); void resourceClicked(KoResource *resource); public Q_SLOTS: void updateViewSettings(); + /// sets the icon size. Used by slider in view menu + void setIconSize(int newSize); + + /// saves the icon size for the presets. called by the horizontal slider release event. + void saveIconSize(); + private Q_SLOTS: void notifyConfigChanged(); protected: virtual void resizeEvent(QResizeEvent* event); private: KoResourceItemChooser *m_chooser; KisPresetDelegate* m_delegate; ViewMode m_mode; QSharedPointer m_adapter; }; #endif // KIS_ITEM_CHOOSER_H_ diff --git a/libs/ui/widgets/kis_preset_selector_strip.cpp b/libs/ui/widgets/kis_preset_selector_strip.cpp index 01aa807ca0..d76ef07c13 100644 --- a/libs/ui/widgets/kis_preset_selector_strip.cpp +++ b/libs/ui/widgets/kis_preset_selector_strip.cpp @@ -1,71 +1,109 @@ /* * Copyright (c) 2011 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_preset_selector_strip.h" #include "KoResourceModel.h" #include "KoResourceItemView.h" #include "KoResourceItemChooser.h" #include #include #include #include KisPresetSelectorStrip::KisPresetSelectorStrip(QWidget* parent) : QWidget(parent) { setupUi(this); - smallPresetChooser->showButtons(false); - smallPresetChooser->setViewMode(KisPresetChooser::STRIP); + smallPresetChooser->showButtons(false); //loading and saving buttons. don't need these with the brush editor + smallPresetChooser->setViewMode(KisPresetChooser::DETAIL); // set to details view by default to see names + smallPresetChooser->showTaggingBar(true); m_resourceItemView = smallPresetChooser->itemChooser()->itemView(); - + /* This is an heuristic to fill smallPresetChooser with only the presets * for the paintop that comes selected by default: Pixel Brush. */ const QString PIXEL_BRUSH_ID = "paintbrush"; m_currentPaintopID = PIXEL_BRUSH_ID; + + + // hide the left and right arrows that are used by the "strip" view by default + rightScrollBtn->hide(); + leftScrollBtn->hide(); + } KisPresetSelectorStrip::~KisPresetSelectorStrip() { } void KisPresetSelectorStrip::setPresetFilter(const QString& paintOpId) { smallPresetChooser->setPresetFilter(paintOpId); if (m_currentPaintopID != paintOpId) { m_resourceItemView->scrollTo(m_resourceItemView->model()->index(0, 0)); m_currentPaintopID = paintOpId; } } + void KisPresetSelectorStrip::on_leftScrollBtn_pressed() { // Deciding how far beyond the left margin (10 pixels) was an arbitrary decision QPoint beyondLeftMargin(-10, 0); m_resourceItemView->scrollTo(m_resourceItemView->indexAt(beyondLeftMargin), QAbstractItemView::EnsureVisible); } void KisPresetSelectorStrip::on_rightScrollBtn_pressed() { // Deciding how far beyond the right margin to put the point (10 pixels) was an arbitrary decision QPoint beyondRightMargin(10 + m_resourceItemView->viewport()->width(), 0); m_resourceItemView->scrollTo(m_resourceItemView->indexAt(beyondRightMargin), QAbstractItemView::EnsureVisible); } + +void KisPresetSelectorStrip::slotThumbnailMode() +{ + smallPresetChooser->setViewMode(KisPresetChooser::THUMBNAIL); // set to details view by default to see names + m_resourceItemView = smallPresetChooser->itemChooser()->itemView(); +} + +void KisPresetSelectorStrip::slotDetailMode() +{ + smallPresetChooser->setViewMode(KisPresetChooser::DETAIL); // set to details view by default to see names + m_resourceItemView = smallPresetChooser->itemChooser()->itemView(); +} + + +int KisPresetSelectorStrip::iconSize() +{ + return smallPresetChooser->iconSize(); +} + + +void KisPresetSelectorStrip::slotSetIconSize(int size) +{ + smallPresetChooser->setIconSize(size); +} + +void KisPresetSelectorStrip::slotSaveIconSize() { + smallPresetChooser->saveIconSize(); +} + + diff --git a/libs/ui/widgets/kis_preset_selector_strip.h b/libs/ui/widgets/kis_preset_selector_strip.h index d7f03d1e57..92a5aa8bd0 100644 --- a/libs/ui/widgets/kis_preset_selector_strip.h +++ b/libs/ui/widgets/kis_preset_selector_strip.h @@ -1,67 +1,84 @@ /* * 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. */ #ifndef KIS_PRESET_SELECTOR_STRIP_H #define KIS_PRESET_SELECTOR_STRIP_H #include #include "ui_wdgpresetselectorstrip.h" class KoResourceItemView; /** * * KisPresetSelectorStrip is a composite widget around KisPresetChooser. It provides * a strip of icons with two scroll buttons at the sides and a small delete button * that appears when a user selects a preset icon. * * KisPresetSelectorStrip makes it possible to quickly select and modify presets. * * Note that KisPresetSelectorStrip uses the QObject tree to access properties of the contained * classes, and uses heuristics to approximate pixel offsets, times, and other * properties that cannot be accessed through the QObject tree. * */ class KisPresetSelectorStrip : public QWidget, public Ui::WdgPresetSelectorStrip { Q_OBJECT public: KisPresetSelectorStrip(QWidget *parent); virtual ~KisPresetSelectorStrip(); void setPresetFilter(const QString& paintOpId); + int iconSize(); + void setIconSize(int size); + +public Q_SLOTS: + + /// saving the icon base size. This affects all preset selectors + /// outside UI elements adjusting icon size + void slotSetIconSize(int size); + + /// saves the icon size to the config file + /// when UI element is released, it is ok to save icon size to config + void slotSaveIconSize(); + private Q_SLOTS: /// Scrolls the strip's item view to the left void on_leftScrollBtn_pressed(); /// Scrolls the strip's item view to the right void on_rightScrollBtn_pressed(); + /// Changes the preset list view type + void slotThumbnailMode(); + void slotDetailMode(); + private: /** * This is a workaround to access members of KisPresetChooser using the QObject tree * instead of class methods */ KoResourceItemView* m_resourceItemView; QString m_currentPaintopID; }; #endif // KIS_PRESET_SELECTOR_STRIP_H diff --git a/libs/ui/widgets/kis_tool_options_popup.cpp b/libs/ui/widgets/kis_tool_options_popup.cpp index 0496b64daf..58664e652e 100644 --- a/libs/ui/widgets/kis_tool_options_popup.cpp +++ b/libs/ui/widgets/kis_tool_options_popup.cpp @@ -1,197 +1,196 @@ /* This file is part of the KDE project * Copyright (C) 2015 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "widgets/kis_tool_options_popup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" struct KisToolOptionsPopup::Private { public: QFont smallFont; bool detached; bool ignoreHideEvents; QRect detachedGeometry; QList > currentWidgetList; QSet currentAuxWidgets; QWidget *hiderWidget; // non current widgets are hidden by being children of this QGridLayout *housekeeperLayout; void recreateLayout(const QList > &optionWidgetList) { Q_FOREACH (QPointer widget, currentWidgetList) { if (!widget.isNull() && widget && hiderWidget) { widget->setParent(hiderWidget); } } qDeleteAll(currentAuxWidgets); currentAuxWidgets.clear(); currentWidgetList = optionWidgetList; // need to unstretch row that have previously been stretched housekeeperLayout->setRowStretch(housekeeperLayout->rowCount()-1, 0); int cnt = 0; QFrame *s; QLabel *l; housekeeperLayout->setHorizontalSpacing(0); housekeeperLayout->setVerticalSpacing(2); int specialCount = 0; Q_FOREACH (QPointer widget, currentWidgetList) { if (widget.isNull() || widget->objectName().isEmpty()) { continue; // skip this docker in release build when assert don't crash } widget->setMinimumWidth(300); if (!widget->windowTitle().isEmpty()) { housekeeperLayout->addWidget(l = new QLabel(widget->windowTitle()), cnt++, 0); currentAuxWidgets.insert(l); } housekeeperLayout->addWidget(widget, cnt++, 0); QLayout *subLayout = widget->layout(); if (subLayout) { for (int i = 0; i < subLayout->count(); ++i) { QWidget *spacerWidget = subLayout->itemAt(i)->widget(); if (spacerWidget && spacerWidget->objectName().contains("SpecialSpacer")) { specialCount++; } } } widget->show(); if (widget != currentWidgetList.last()) { housekeeperLayout->addWidget(s = new QFrame(), cnt++, 0); s->setFrameShape(QFrame::HLine); currentAuxWidgets.insert(s); } } if (specialCount == currentWidgetList.count() || qApp->applicationName().contains("krita")) { housekeeperLayout->setRowStretch(cnt, 10000); } housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); housekeeperLayout->invalidate(); } }; KisToolOptionsPopup::KisToolOptionsPopup(QWidget *parent) : QWidget(parent) , d(new Private()) { setObjectName("KisToolOptionsPopup"); KConfigGroup group( KSharedConfig::openConfig(), "GUI"); setFont(KoDockRegistry::dockFont()); KisConfig cfg; d->detached = !cfg.paintopPopupDetached(); d->ignoreHideEvents = false; d->housekeeperLayout = new QGridLayout(); d->housekeeperLayout->setContentsMargins(4,4,4,0); setLayout(d->housekeeperLayout); d->housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); d->hiderWidget = new QWidget(this); d->hiderWidget->setVisible(false); } KisToolOptionsPopup::~KisToolOptionsPopup() { delete d; } void KisToolOptionsPopup::newOptionWidgets(const QList > &optionWidgetList) { d->recreateLayout(optionWidgetList); } void KisToolOptionsPopup::contextMenuEvent(QContextMenuEvent *e) { QMenu menu(this); QAction* action = menu.addAction(d->detached ? i18n("Attach to Toolbar") : i18n("Detach from Toolbar")); connect(action, SIGNAL(triggered()), this, SLOT(switchDetached())); menu.exec(e->globalPos()); } void KisToolOptionsPopup::hideEvent(QHideEvent *event) { if (d->ignoreHideEvents) { return; } if (d->detached) { d->detachedGeometry = window()->geometry(); } QWidget::hideEvent(event); } void KisToolOptionsPopup::showEvent(QShowEvent *) { if (d->detached) { window()->setGeometry(d->detachedGeometry); } } void KisToolOptionsPopup::switchDetached(bool show) { if (parentWidget()) { d->detached = !d->detached; if (d->detached) { d->ignoreHideEvents = true; - parentWidget()->setWindowFlags(Qt::Tool); + if (show) { parentWidget()->show(); } d->ignoreHideEvents = false; } else { - parentWidget()->setWindowFlags(Qt::Popup); KisConfig cfg; parentWidget()->hide(); } KisConfig cfg; cfg.setToolOptionsPopupDetached(d->detached); } } diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt index 0e90f601bf..52c5798d0f 100644 --- a/libs/widgets/CMakeLists.txt +++ b/libs/widgets/CMakeLists.txt @@ -1,110 +1,112 @@ add_subdirectory( tests ) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(kritawidgets_LIB_SRCS KoGradientEditWidget.cpp KoVBox.cpp KoDialog.cpp KoGlobal.cpp KoZoomWidget.cpp KoTagToolButton.cpp KoTagChooserWidget.cpp KoTagFilterWidget.cpp KoResourceTaggingManager.cpp KoResourceItemChooserContextMenu.cpp KoAspectButton.cpp KoPagePreviewWidget.cpp KoPositionSelector.cpp KoSliderCombo.cpp KoColorPopupButton.cpp KoConfigAuthorPage.cpp KoUnitDoubleSpinBox.cpp KoZoomAction.cpp KoZoomController.cpp KoZoomInput.cpp KoZoomHandler.cpp KoZoomMode.cpp KoDpi.cpp KoGlobal.cpp KoColorPatch.cpp KoColorPopupAction.cpp KoColorSetWidget.cpp KoColorSlider.cpp KoEditColorSetDialog.cpp KoTriangleColorSelector.cpp KoResourcePopupAction.cpp KoFillConfigWidget.cpp KoIconToolTip.cpp KoResourceItemChooser.cpp KoResourceItemChooserSync.cpp KoResourceSelector.cpp KoResourceModel.cpp KoResourceItemDelegate.cpp KoResourceItemView.cpp KoResourceTagStore.cpp KoRuler.cpp #KoRulerController.cpp KoItemToolTip.cpp KoCheckerBoardPainter.cpp KoResourceServerAdapter.cpp KoResourceServerProvider.cpp KoLineStyleSelector.cpp KoLineStyleItemDelegate.cpp KoLineStyleModel.cpp KoDockWidgetTitleBar.cpp KoDockWidgetTitleBarButton.cpp KoResourceFiltering.cpp KoResourceModelBase.cpp KoToolBoxButton.cpp KoToolBox.cpp KoToolBoxDocker.cpp KoToolBoxFactory.cpp KoToolDocker.cpp KoPageLayoutWidget.cpp KoPageLayoutDialog.cpp KoShadowConfigWidget.cpp KoStrokeConfigWidget.cpp KoMarkerSelector.cpp KoMarkerModel.cpp KoMarkerItemDelegate.cpp KoDocumentInfoDlg.cpp KoGlobal.cpp KoTableView.cpp WidgetsDebug.cpp kis_file_name_requester.cpp kis_double_parse_spin_box.cpp kis_double_parse_unit_spin_box.cpp kis_int_parse_spin_box.cpp + + KisColorSelectorInterface.cpp ) ki18n_wrap_ui( kritawidgets_LIB_SRCS KoConfigAuthorPage.ui koDocumentInfoAboutWidget.ui koDocumentInfoAuthorWidget.ui KoEditColorSet.ui wdg_file_name_requester.ui KoPageLayoutWidget.ui KoShadowConfigWidget.ui ) add_library(kritawidgets SHARED ${kritawidgets_LIB_SRCS}) generate_export_header(kritawidgets BASE_NAME kritawidgets) target_link_libraries(kritawidgets kritaodf kritaflake kritapigment kritawidgetutils Qt5::PrintSupport KF5::CoreAddons KF5::ConfigGui KF5::GuiAddons KF5::WidgetsAddons KF5::ConfigCore KF5::Completion) if(X11_FOUND) target_link_libraries(kritawidgets Qt5::X11Extras ${X11_LIBRARIES}) endif() set_target_properties(kritawidgets PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritawidgets ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/widgets/KisColorSelectorInterface.cpp b/libs/widgets/KisColorSelectorInterface.cpp new file mode 100644 index 0000000000..840b621023 --- /dev/null +++ b/libs/widgets/KisColorSelectorInterface.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 Boudewijn Rempt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include diff --git a/libs/widgets/KisColorSelectorInterface.h b/libs/widgets/KisColorSelectorInterface.h new file mode 100644 index 0000000000..f393c7feb5 --- /dev/null +++ b/libs/widgets/KisColorSelectorInterface.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Boudewijn Rempt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KISCOLORSELECTORINTERFACE_H +#define KISCOLORSELECTORINTERFACE_H + +#include "kritawidgets_export.h" + +#include +#include + +class KoColorDisplayRendererInterface; + +class KRITAWIDGETS_EXPORT KisColorSelectorInterface : public QWidget { + Q_OBJECT +public: + KisColorSelectorInterface(QWidget *parent = 0) + : QWidget(parent) + {} + virtual ~KisColorSelectorInterface() {} + virtual void setConfig(bool forceCircular, bool forceSelfUpdate) + { + Q_UNUSED(forceCircular); + Q_UNUSED(forceSelfUpdate); + } + virtual void setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) + { + Q_UNUSED(displayRenderer); + } + + virtual KoColor getCurrentColor() const = 0; + +Q_SIGNALS: + void sigNewColor(const KoColor &c); + +public Q_SLOTS: + virtual void slotSetColor(const KoColor &c) = 0; +}; + +#endif // KISCOLORSELECTORINTERFACE_H diff --git a/libs/widgets/KoColorPopupAction.cpp b/libs/widgets/KoColorPopupAction.cpp index 5582d1dffd..8eba94505c 100644 --- a/libs/widgets/KoColorPopupAction.cpp +++ b/libs/widgets/KoColorPopupAction.cpp @@ -1,246 +1,246 @@ /* This file is part of the KDE project * Copyright (c) 2007 C. Boemann * Copyright (C) 2007 Fredy Yanardi * * 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 "KoColorPopupAction.h" #include "KoColorSetWidget.h" #include "KoTriangleColorSelector.h" #include "KoColorSlider.h" #include "KoCheckerBoardPainter.h" #include "KoResourceServer.h" #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include #include class KoColorPopupAction::KoColorPopupActionPrivate { public: KoColorPopupActionPrivate() : colorSetWidget(0), colorChooser(0), opacitySlider(0), menu(0), checkerPainter(4) , showFilter(true), applyMode(true), firstTime(true) {} ~KoColorPopupActionPrivate() { delete colorSetWidget; delete colorChooser; delete opacitySlider; delete menu; } KoColor currentColor; KoColor buddyColor; KoColorSetWidget *colorSetWidget; KoTriangleColorSelector * colorChooser; KoColorSlider * opacitySlider; QMenu *menu; KoCheckerBoardPainter checkerPainter; bool showFilter; bool applyMode; bool firstTime; }; KoColorPopupAction::KoColorPopupAction(QObject *parent) : QAction(parent), d(new KoColorPopupActionPrivate()) { d->menu = new QMenu(); QWidget *widget = new QWidget(d->menu); QWidgetAction *wdgAction = new QWidgetAction(d->menu); d->colorSetWidget = new KoColorSetWidget(widget); d->colorChooser = new KoTriangleColorSelector( widget ); // prevent mouse release on color selector from closing popup d->colorChooser->setAttribute( Qt::WA_NoMousePropagation ); d->opacitySlider = new KoColorSlider( Qt::Vertical, widget ); d->opacitySlider->setFixedWidth(25); d->opacitySlider->setRange(0, 255); d->opacitySlider->setValue(255); d->opacitySlider->setToolTip( i18n( "Opacity" ) ); QGridLayout * layout = new QGridLayout( widget ); layout->addWidget( d->colorSetWidget, 0, 0, 1, -1 ); layout->addWidget( d->colorChooser, 1, 0 ); layout->addWidget( d->opacitySlider, 1, 1 ); layout->setMargin(4); wdgAction->setDefaultWidget(widget); d->menu->addAction(wdgAction); setMenu(d->menu); new QHBoxLayout(d->menu); d->menu->layout()->addWidget(widget); d->menu->layout()->setMargin(0); connect(this, SIGNAL(triggered()), this, SLOT(emitColorChanged())); connect(d->colorSetWidget, SIGNAL(colorChanged(const KoColor &, bool)), this, SLOT(colorWasSelected(const KoColor &, bool))); connect( d->colorChooser, SIGNAL( colorChanged( const QColor &) ), this, SLOT( colorWasEdited( const QColor &) ) ); connect( d->opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(opacityWasChanged(int))); } KoColorPopupAction::~KoColorPopupAction() { delete d; } void KoColorPopupAction::setCurrentColor( const KoColor &color ) { - d->colorChooser->setRealColor( color ); + d->colorChooser->slotSetColor( color ); KoColor minColor( color ); d->currentColor = minColor; KoColor maxColor( color ); minColor.setOpacity( OPACITY_TRANSPARENT_U8 ); maxColor.setOpacity( OPACITY_OPAQUE_U8 ); d->opacitySlider->blockSignals( true ); d->opacitySlider->setColors( minColor, maxColor ); d->opacitySlider->setValue( color.opacityU8() ); d->opacitySlider->blockSignals( false ); updateIcon(); } void KoColorPopupAction::setCurrentColor( const QColor &_color ) { #ifndef NDEBUG if (!_color.isValid()) { warnWidgets << "Invalid color given, defaulting to black"; } #endif const QColor color(_color.isValid() ? _color : QColor(0,0,0,255)); setCurrentColor(KoColor(color, KoColorSpaceRegistry::instance()->rgb8() )); } QColor KoColorPopupAction::currentColor() const { return d->currentColor.toQColor(); } KoColor KoColorPopupAction::currentKoColor() const { return d->currentColor; } void KoColorPopupAction::updateIcon() { QSize iconSize; QToolButton *toolButton = dynamic_cast(parentWidget()); if (toolButton) { iconSize = QSize(toolButton->iconSize()); } else { iconSize = QSize(16, 16); } // This must be a QImage, as drawing to a QPixmap outside the // UI thread will cause sporadic crashes. QImage pm; if (icon().isNull()) { d->applyMode = false; } if(d->applyMode) { pm = icon().pixmap(iconSize).toImage(); if (pm.isNull()) { pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied); pm.fill(Qt::transparent); } QPainter p(&pm); p.fillRect(0, iconSize.height() - 4, iconSize.width(), 4, d->currentColor.toQColor()); p.end(); } else { pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied); pm.fill(Qt::transparent); QPainter p(&pm); d->checkerPainter.paint(p, QRect(QPoint(),iconSize)); p.fillRect(0, 0, iconSize.width(), iconSize.height(), d->currentColor.toQColor()); p.end(); } setIcon(QIcon(QPixmap::fromImage(pm))); } void KoColorPopupAction::emitColorChanged() { emit colorChanged( d->currentColor ); } void KoColorPopupAction::colorWasSelected(const KoColor &color, bool final) { d->currentColor = color; if (final) { menu()->hide(); emitColorChanged(); } updateIcon(); } void KoColorPopupAction::colorWasEdited( const QColor &color ) { d->currentColor = KoColor( color, KoColorSpaceRegistry::instance()->rgb8() ); quint8 opacity = d->opacitySlider->value(); d->currentColor.setOpacity( opacity ); KoColor minColor = d->currentColor; minColor.setOpacity( OPACITY_TRANSPARENT_U8 ); KoColor maxColor = minColor; maxColor.setOpacity( OPACITY_OPAQUE_U8 ); d->opacitySlider->setColors( minColor, maxColor ); emitColorChanged(); updateIcon(); } void KoColorPopupAction::opacityWasChanged( int opacity ) { d->currentColor.setOpacity( quint8(opacity) ); emitColorChanged(); } void KoColorPopupAction::slotTriggered(bool) { if (d->firstTime) { KoResourceServer* srv = KoResourceServerProvider::instance()->paletteServer(false); QList palettes = srv->resources(); if (!palettes.empty()) { d->colorSetWidget->setColorSet(palettes.first()); } d->firstTime = false; } } diff --git a/libs/widgets/KoResourceItemChooserSync.cpp b/libs/widgets/KoResourceItemChooserSync.cpp index befe316806..dc3dd146e5 100644 --- a/libs/widgets/KoResourceItemChooserSync.cpp +++ b/libs/widgets/KoResourceItemChooserSync.cpp @@ -1,64 +1,64 @@ /* This file is part of the KDE project Copyright (c) 2014 Sven Langkamp 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 "KoResourceItemChooserSync.h" #include Q_GLOBAL_STATIC(KoResourceItemChooserSync, s_instance) struct Q_DECL_HIDDEN KoResourceItemChooserSync::Private { int baseLength; }; KoResourceItemChooserSync::KoResourceItemChooserSync() : d(new Private) { d->baseLength = 50; } KoResourceItemChooserSync::~KoResourceItemChooserSync() { } KoResourceItemChooserSync* KoResourceItemChooserSync::instance() { return s_instance; } int KoResourceItemChooserSync::baseLength() { return d->baseLength; } void KoResourceItemChooserSync::setBaseLength(int length) { - d->baseLength = qBound(50, length, 150); + d->baseLength = qBound(25, length, 100); emit baseLenghtChanged(d->baseLength); } diff --git a/libs/widgets/KoResourceServer.h b/libs/widgets/KoResourceServer.h index 80e95ba333..6d54f0c9e3 100644 --- a/libs/widgets/KoResourceServer.h +++ b/libs/widgets/KoResourceServer.h @@ -1,713 +1,713 @@ /* This file is part of the KDE project Copyright (c) 1999 Matthias Elter Copyright (c) 2003 Patrick Julien Copyright (c) 2005 Sven Langkamp Copyright (c) 2007 Jan Hambrecht Copyright (C) 2011 Srikanth Tiyyagura Copyright (c) 2013 Sascha Suelzer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KORESOURCESERVER_H #define KORESOURCESERVER_H #include #include #include #include #include #include #include #include #include "resources/KoResource.h" #include "KoResourceServerPolicies.h" #include "KoResourceServerObserver.h" #include "KoResourceTagStore.h" #include "KoResourcePaths.h" #include "kritawidgets_export.h" #include "WidgetsDebug.h" class KoResource; /** * KoResourceServerBase is the base class of all resource servers */ class KRITAWIDGETS_EXPORT KoResourceServerBase { public: /** * Constructs a KoResourceServerBase * @param resource type, has to be the same as used by KoResourcePaths * @param extensions the file extensions separate by ':', e.g. "*.kgr:*.svg:*.ggr" */ KoResourceServerBase(const QString& type, const QString& extensions) : m_type(type) , m_extensions(extensions) { } virtual ~KoResourceServerBase() {} virtual int resourceCount() const = 0; virtual void loadResources(QStringList filenames) = 0; virtual QStringList blackListedFiles() const = 0; virtual QStringList queryResources(const QString &query) const = 0; QString type() const { return m_type; } /** * File extensions for resources of the server * @returns the file extensions separated by ':', e.g. "*.kgr:*.svg:*.ggr" */ QString extensions() const { return m_extensions; } QStringList fileNames() const { QStringList extensionList = m_extensions.split(':'); QStringList fileNames; foreach (const QString &extension, extensionList) { fileNames += KoResourcePaths::findAllResources(type().toLatin1(), extension, KoResourcePaths::Recursive); } return fileNames; } protected: friend class KoResourceTagStore; virtual KoResource *byMd5(const QByteArray &md5) const = 0; virtual KoResource *byFileName(const QString &fileName) const = 0; private: QString m_type; QString m_extensions; protected: QMutex m_loadLock; }; /** * KoResourceServer manages the resources of one type. It stores, * loads and saves the resources. To keep track of changes the server * can be observed with a KoResourceServerObserver * * The \p Policy template parameter defines the way how the lifetime * of a resource is handled. There are to predefined policies: * * o PointerStoragePolicy --- usual pointers with ownership over * the resource. * o SharedPointerStoragePolicy --- shared pointers. The server does no * extra handling for the lifetime of * the resource. * * Use the former for usual resources and the latter for shared pointer based * ones. */ template > class KoResourceServer : public KoResourceServerBase { public: typedef typename Policy::PointerType PointerType; typedef KoResourceServerObserver ObserverType; KoResourceServer(const QString& type, const QString& extensions) : KoResourceServerBase(type, extensions) { m_blackListFile = KoResourcePaths::locateLocal("data", type + ".blacklist"); m_blackListFileNames = readBlackListFile(); m_tagStore = new KoResourceTagStore(this); m_tagStore->loadTags(); } virtual ~KoResourceServer() { if (m_tagStore) { delete m_tagStore; } Q_FOREACH (ObserverType* observer, m_observers) { observer->unsetResourceServer(); } Q_FOREACH (PointerType res, m_resources) { Policy::deleteResource(res); } m_resources.clear(); } int resourceCount() const { return m_resources.size(); } /** * Loads a set of resources and adds them to the resource server. * If a filename appears twice the resource will only be added once. Resources that can't * be loaded or and invalid aren't added to the server. * @param filenames list of filenames to be loaded */ void loadResources(QStringList filenames) { QStringList uniqueFiles; while (!filenames.empty()) { QString front = filenames.first(); filenames.pop_front(); // In the save location, people can use sub-folders... And then they probably want // to load both versions! See https://bugs.kde.org/show_bug.cgi?id=321361. QString fname; if (front.contains(saveLocation())) { fname = front.split(saveLocation())[1]; } else { fname = QFileInfo(front).fileName(); } // XXX: Don't load resources with the same filename. Actually, we should look inside // the resource to find out whether they are really the same, but for now this // will prevent the same brush etc. showing up twice. if (!uniqueFiles.contains(fname)) { m_loadLock.lock(); uniqueFiles.append(fname); QList resources = createResources(front); Q_FOREACH (PointerType resource, resources) { Q_CHECK_PTR(resource); if (resource->load() && resource->valid() && !resource->md5().isEmpty()) { QByteArray md5 = resource->md5(); m_resourcesByMd5[md5] = resource; m_resourcesByFilename[resource->shortFilename()] = resource; if (resource->name().isEmpty()) { resource->setName(fname); } if (m_resourcesByName.contains(resource->name())) { resource->setName(resource->name() + "(" + resource->shortFilename() + ")"); } m_resourcesByName[resource->name()] = resource; notifyResourceAdded(resource); } else { warnWidgets << "Loading resource " << front << "failed"; Policy::deleteResource(resource); } } m_loadLock.unlock(); } } m_resources = sortedResources(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } debugWidgets << "done loading resources for type " << type(); } /// Adds an already loaded resource to the server bool addResource(PointerType resource, bool save = true, bool infront = false) { if (!resource->valid()) { warnWidgets << "Tried to add an invalid resource!"; return false; } if (save) { QFileInfo fileInfo(resource->filename()); QDir d(fileInfo.path()); if (!d.exists()) { d.mkdir(fileInfo.path()); } if (fileInfo.exists()) { QString filename = fileInfo.path() + "/" + fileInfo.baseName() + "XXXXXX" + "." + fileInfo.suffix(); debugWidgets << "fileName is " << filename; QTemporaryFile file(filename); if (file.open()) { debugWidgets << "now " << file.fileName(); resource->setFilename(file.fileName()); } } if (!resource->save()) { warnWidgets << "Could not save resource!"; return false; } } Q_ASSERT(!resource->filename().isEmpty() || !resource->name().isEmpty()); if (resource->filename().isEmpty()) { resource->setFilename(resource->name()); } else if (resource->name().isEmpty()) { resource->setName(resource->filename()); } m_resourcesByFilename[resource->shortFilename()] = resource; m_resourcesByMd5[resource->md5()] = resource; m_resourcesByName[resource->name()] = resource; if (infront) { m_resources.insert(0, resource); } else { m_resources.append(resource); } notifyResourceAdded(resource); return true; } - + /*Removes a given resource from the blacklist. */ bool removeFromBlacklist(PointerType resource) { if (m_blackListFileNames.contains(resource->filename())) { m_blackListFileNames.removeAll(resource->filename()); writeBlackListFile(); } else{ warnWidgets<<"Doesn't contain filename"; return false; } - - + + //then return true// return true; } /// Remove a resource from Resource Server but not from a file bool removeResourceFromServer(PointerType resource){ if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } m_resourcesByMd5.remove(resource->md5()); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); Policy::deleteResource(resource); return true; } /// Remove a resource from the resourceserver and blacklist it bool removeResourceAndBlacklist(PointerType resource) { if ( !m_resourcesByFilename.contains( resource->shortFilename() ) ) { return false; } m_resourcesByMd5.remove(resource->md5()); m_resourcesByName.remove(resource->name()); m_resourcesByFilename.remove(resource->shortFilename()); m_resources.removeAt(m_resources.indexOf(resource)); m_tagStore->removeResource(resource); notifyRemovingResource(resource); m_blackListFileNames.append(resource->filename()); writeBlackListFile(); Policy::deleteResource(resource); return true; } QList resources() { m_loadLock.lock(); QList resourceList = m_resources; Q_FOREACH (PointerType r, m_resourceBlackList) { resourceList.removeOne(r); } m_loadLock.unlock(); return resourceList; } /// Returns path where to save user defined and imported resources to virtual QString saveLocation() { return KoResourcePaths::saveLocation(type().toLatin1()); } /** * Creates a new resource from a given file and adds them to the resource server * The base implementation does only load one resource per file, override to implement collections * @param filename file name of the resource file to be imported * @param fileCreation decides whether to create the file in the saveLocation() directory */ virtual bool importResourceFile(const QString & filename , bool fileCreation=true) { QFileInfo fi(filename); if (!fi.exists()) return false; if ( fi.size() == 0) return false; PointerType resource = createResource( filename ); resource->load(); if (!resource->valid()) { warnWidgets << "Import failed! Resource is not valid"; Policy::deleteResource(resource); return false; } if (fileCreation) { Q_ASSERT(!resource->defaultFileExtension().isEmpty()); Q_ASSERT(!saveLocation().isEmpty()); QString newFilename = saveLocation() + fi.baseName() + resource->defaultFileExtension(); QFileInfo fileInfo(newFilename); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation() + fi.baseName() + QString("%1").arg(i) + resource->defaultFileExtension()); i++; } resource->setFilename(fileInfo.filePath()); } if(!addResource(resource)) { Policy::deleteResource(resource); } return true; } /// Removes the resource file from the resource server virtual void removeResourceFile(const QString & filename) { QFileInfo fi(filename); PointerType resource = resourceByFilename(fi.fileName()); if (!resource) { warnWidgets << "Resource file do not exist "; return; } if (!removeResourceFromServer(resource)) return; } /** * Addes an observer to the server * @param observer the observer to be added * @param notifyLoadedResources determines if the observer should be notified about the already loaded resources */ void addObserver(ObserverType* observer, bool notifyLoadedResources = true) { m_loadLock.lock(); if(observer && !m_observers.contains(observer)) { m_observers.append(observer); if(notifyLoadedResources) { Q_FOREACH (PointerType resource, m_resourcesByFilename) { observer->resourceAdded(resource); } } } m_loadLock.unlock(); } /** * Removes an observer from the server * @param observer the observer to be removed */ void removeObserver(ObserverType* observer) { int index = m_observers.indexOf( observer ); if( index < 0 ) return; m_observers.removeAt( index ); } PointerType resourceByFilename(const QString& filename) const { if (m_resourcesByFilename.contains(filename)) { return m_resourcesByFilename[filename]; } return 0; } PointerType resourceByName( const QString& name ) const { if (m_resourcesByName.contains(name)) { return m_resourcesByName[name]; } return 0; } PointerType resourceByMD5(const QByteArray& md5) const { return m_resourcesByMd5.value(md5); } /** * Call after changing the content of a resource; * Notifies the connected views. */ void updateResource( PointerType resource ) { notifyResourceChanged(resource); } QStringList blackListedFiles() const { return m_blackListFileNames; } void removeBlackListedFiles() { QStringList remainingFiles; // Files that can't be removed e.g. no rights will stay blacklisted Q_FOREACH (const QString &filename, m_blackListFileNames) { QFile file( filename ); if( ! file.remove() ) { remainingFiles.append(filename); } } m_blackListFileNames = remainingFiles; writeBlackListFile(); } QStringList tagNamesList() const { return m_tagStore->tagNamesList(); } // don't use these method directly since it doesn't update views! void addTag( KoResource* resource,const QString& tag) { m_tagStore->addTag(resource,tag); } // don't use these method directly since it doesn't update views! void delTag( KoResource* resource,const QString& tag) { m_tagStore->delTag(resource,tag); } QStringList searchTag(const QString& lineEditText) { return m_tagStore->searchTag(lineEditText); } void tagCategoryAdded(const QString& tag) { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagAddition(tag); } } void tagCategoryRemoved(const QString& tag) { m_tagStore->delTag(tag); m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTagRemoval(tag); } } void tagCategoryMembersChanged() { m_tagStore->serializeTags(); Q_FOREACH (ObserverType* observer, m_observers) { observer->syncTaggedResourceView(); } } QStringList queryResources(const QString &query) const { return m_tagStore->searchTag(query); } QStringList assignedTagsList(KoResource* resource) const { return m_tagStore->assignedTagsList(resource); } /** * Create one or more resources from a single file. By default one resource is created. * Overide to create more resources from the file. * @param filename the filename of the resource or resource collection */ virtual QList createResources( const QString & filename ) { QList createdResources; createdResources.append(createResource(filename)); return createdResources; } virtual PointerType createResource( const QString & filename ) = 0; /// Return the currently stored resources in alphabetical order, overwrite for customized sorting virtual QList sortedResources() { QMap sortedNames; Q_FOREACH (const QString &name, m_resourcesByName.keys()) { sortedNames.insert(name.toLower(), m_resourcesByName[name]); } return sortedNames.values(); } protected: void notifyResourceAdded(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceAdded(resource); } } void notifyRemovingResource(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->removingResource(resource); } } void notifyResourceChanged(PointerType resource) { Q_FOREACH (ObserverType* observer, m_observers) { observer->resourceChanged(resource); } } /// Reads the xml file and returns the filenames as a list QStringList readBlackListFile() { QStringList filenameList; QFile f(m_blackListFile); if (!f.open(QIODevice::ReadOnly)) { return filenameList; } QDomDocument doc; if (!doc.setContent(&f)) { warnWidgets << "The file could not be parsed."; return filenameList; } QDomElement root = doc.documentElement(); if (root.tagName() != "resourceFilesList") { warnWidgets << "The file doesn't seem to be of interest."; return filenameList; } QDomElement file = root.firstChildElement("file"); while (!file.isNull()) { QDomNode n = file.firstChild(); QDomElement e = n.toElement(); if (e.tagName() == "name") { filenameList.append((e.text()).replace(QString("~"),QDir::homePath())); } file = file.nextSiblingElement("file"); } return filenameList; } /// write the blacklist file entries to an xml file void writeBlackListFile() { QFile f(m_blackListFile); if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { warnWidgets << "Cannot write meta information to '" << m_blackListFile << "'." << endl; return; } QDomDocument doc; QDomElement root; QDomDocument docTemp("m_blackListFile"); doc = docTemp; doc.appendChild(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"")); root = doc.createElement("resourceFilesList"); doc.appendChild(root); Q_FOREACH (QString filename, m_blackListFileNames) { QDomElement fileEl = doc.createElement("file"); QDomElement nameEl = doc.createElement("name"); QDomText nameText = doc.createTextNode(filename.replace(QDir::homePath(),QString("~"))); nameEl.appendChild(nameText); fileEl.appendChild(nameEl); root.appendChild(fileEl); } QTextStream metastream(&f); metastream << doc.toString(); f.close(); } protected: KoResource* byMd5(const QByteArray &md5) const { return Policy::toResourcePointer(resourceByMD5(md5)); } KoResource* byFileName(const QString &fileName) const { return Policy::toResourcePointer(resourceByFilename(fileName)); } private: QHash m_resourcesByName; QHash m_resourcesByFilename; QHash m_resourcesByMd5; QList m_resourceBlackList; QList m_resources; ///< list of resources in order of addition QList m_observers; QString m_blackListFile; QStringList m_blackListFileNames; KoResourceTagStore* m_tagStore; }; template > class KoResourceServerSimpleConstruction : public KoResourceServer { public: KoResourceServerSimpleConstruction(const QString& type, const QString& extensions) : KoResourceServer(type, extensions) { } typename KoResourceServer::PointerType createResource( const QString & filename ) { return new T(filename); } }; #endif // KORESOURCESERVER_H diff --git a/libs/widgets/KoTagFilterWidget.cpp b/libs/widgets/KoTagFilterWidget.cpp index ae09331dc5..f6866f594e 100644 --- a/libs/widgets/KoTagFilterWidget.cpp +++ b/libs/widgets/KoTagFilterWidget.cpp @@ -1,140 +1,140 @@ /* * This file is part of the KDE project * Copyright (c) 2002 Patrick Julien * Copyright (c) 2007 Jan Hambrecht * Copyright (c) 2007 Sven Langkamp * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2011 José Luis Vergara * Copyright (c) 2013 Sascha Suelzer * * 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 "KoTagFilterWidget.h" #include #include #include #include #include #include class KoTagFilterWidget::Private { public: QString tagSearchBarTooltip_saving_disabled; QString tagSearchBarTooltip_saving_enabled; QLineEdit* tagSearchLineEdit; QPushButton* tagSearchSaveButton; QGridLayout* filterBarLayout; }; KoTagFilterWidget::KoTagFilterWidget(QWidget* parent): QWidget(parent) ,d( new Private()) { d->tagSearchBarTooltip_saving_disabled = i18nc ( "@info:tooltip", - "Entering search terms here will add to, or remove resources from the current tag view." - "To filter based on the partial, case insensitive name of a resource:
" - "partialname or !partialname.
" - "In-/exclusion of other tag sets:
" - "[Tagname] or ![Tagname].
" - "Case sensitive and full name matching in-/exclusion:
" - "\"ExactMatch\" or !\"ExactMatch\".
" + "Entering search terms here will add to, or remove resources from the current tag view." + "

To filter based on the partial, case insensitive name of a resource:
" + "partialname or !partialname.

" + "

In-/exclusion of other tag sets:
" + "[Tagname] or ![Tagname].

" + "

Case sensitive and full name matching in-/exclusion:
" + "\"ExactMatch\" or !\"ExactMatch\".

" "Filter results cannot be saved for the All Presets view.
" - "In this view, pressing Enter or clearing the filter box will restore all items.
" - "Create and/or switch to a different tag if you want to save filtered resources into named sets.
" + "In this view, pressing Enter or clearing the filter box will restore all items.
" + "Create and/or switch to a different tag if you want to save filtered resources into named sets." ); d->tagSearchBarTooltip_saving_enabled = i18nc ( "@info:tooltip", - "Entering search terms here will add to, or remove resources from the current tag view." - "To filter based on the partial, case insensitive name of a resource:
" - "partialname or !partialname.
" - "In-/exclusion of other tag sets:
" - "[Tagname] or ![Tagname].
" - "Case sensitive and full name matching in-/exclusion:
" - "\"ExactMatch\" or !\"ExactMatch\".
" - "Pressing Enter or clicking the Save button will save the changes.
" + "Entering search terms here will add to, or remove resources from the current tag view." + "

To filter based on the partial, case insensitive name of a resource:
" + "partialname or !partialname.

" + "

In-/exclusion of other tag sets:
" + "[Tagname] or ![Tagname].

" + "

Case sensitive and full name matching in-/exclusion:
" + "\"ExactMatch\" or !\"ExactMatch\".

" + "Pressing Enter or clicking the Save button will save the changes." ); QGridLayout* filterBarLayout = new QGridLayout; d->tagSearchLineEdit = new QLineEdit(this); d->tagSearchLineEdit->setClearButtonEnabled(true); d->tagSearchLineEdit->setPlaceholderText(i18n("Enter resource filters here")); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); d->tagSearchLineEdit->setEnabled(true); filterBarLayout->setSpacing(0); filterBarLayout->setMargin(0); filterBarLayout->setColumnStretch(0, 1); filterBarLayout->addWidget(d->tagSearchLineEdit, 0, 0); d->tagSearchSaveButton = new QPushButton(this); d->tagSearchSaveButton->setIcon(koIcon("media-floppy")); d->tagSearchSaveButton->setToolTip(i18nc("@info:tooltip", "Save the currently filtered set as the new members of the current tag.")); d->tagSearchSaveButton->setEnabled(false); filterBarLayout->addWidget(d->tagSearchSaveButton, 0, 1); connect(d->tagSearchSaveButton, SIGNAL(pressed()), this, SLOT(onSaveButtonClicked())); connect(d->tagSearchLineEdit, SIGNAL(returnPressed()), this, SLOT(onSaveButtonClicked())); connect(d->tagSearchLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString))); allowSave(false); this->setLayout(filterBarLayout); } KoTagFilterWidget::~KoTagFilterWidget() { delete d; } void KoTagFilterWidget::allowSave(bool allow) { if (allow) { d->tagSearchSaveButton->show(); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_enabled); } else { d->tagSearchSaveButton->hide(); d->tagSearchLineEdit->setToolTip(d->tagSearchBarTooltip_saving_disabled); } } void KoTagFilterWidget::clear() { d->tagSearchLineEdit->clear(); d->tagSearchSaveButton->setEnabled(false); } void KoTagFilterWidget::onTextChanged(const QString& lineEditText) { d->tagSearchSaveButton->setEnabled(!lineEditText.isEmpty()); emit filterTextChanged(lineEditText); } void KoTagFilterWidget::onSaveButtonClicked() { emit saveButtonClicked(); clear(); } diff --git a/libs/widgets/KoTriangleColorSelector.cpp b/libs/widgets/KoTriangleColorSelector.cpp index bd8d6b8176..ca088b8158 100644 --- a/libs/widgets/KoTriangleColorSelector.cpp +++ b/libs/widgets/KoTriangleColorSelector.cpp @@ -1,444 +1,444 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoTriangleColorSelector.h" #include #include #include #include #include #include #include #include enum CurrentHandle { NoHandle, HueHandle, ValueSaturationHandle }; struct Q_DECL_HIDDEN KoTriangleColorSelector::Private { Private(KoTriangleColorSelector *_q, const KoColorDisplayRendererInterface *_displayRenderer) : q(_q), displayRenderer(_displayRenderer), hue(0), saturation(0), value(0), updateAllowed(true), invalidTriangle(true), lastX(-1), lastY(-1) { } KoTriangleColorSelector *q; const KoColorDisplayRendererInterface *displayRenderer; QPixmap wheelPixmap; QPixmap trianglePixmap; int hue; int saturation; int value; int sizeColorSelector; qreal centerColorSelector; qreal wheelWidthProportion; qreal wheelWidth; qreal wheelNormExt; qreal wheelNormInt; qreal wheelInnerRadius; qreal triangleRadius; qreal triangleLength; qreal triangleHeight; qreal triangleBottom; qreal triangleTop; qreal normExt; qreal normInt; bool updateAllowed; CurrentHandle handle; qreal triangleHandleSize; bool invalidTriangle; int lastX, lastY; QTimer updateTimer; void init(); }; void KoTriangleColorSelector::Private::init() { q->setMinimumHeight( 100 ); q->setMinimumWidth( 100 ); q->setMouseTracking( true ); q->updateTriangleCircleParameters(); updateTimer.setInterval(1); updateTimer.setSingleShot(true); q->connect(&updateTimer, SIGNAL(timeout()), q, SLOT(update())); } KoTriangleColorSelector::KoTriangleColorSelector(QWidget* parent) - : QWidget(parent), + : KisColorSelectorInterface(parent), d(new Private(this, KoDumbColorDisplayRenderer::instance())) { d->init(); } KoTriangleColorSelector::KoTriangleColorSelector(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent) - : QWidget(parent), + : KisColorSelectorInterface(parent), d(new Private(this, displayRenderer)) { d->init(); connect(displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(configurationChanged())); } KoTriangleColorSelector::~KoTriangleColorSelector() { delete d; } void KoTriangleColorSelector::updateTriangleCircleParameters() { d->sizeColorSelector = qMin(width(), height()); d->centerColorSelector = 0.5 * d->sizeColorSelector; d->wheelWidthProportion = 0.25; d->wheelWidth = d->centerColorSelector * d->wheelWidthProportion; d->wheelNormExt = qAbs( d->centerColorSelector ); d->wheelNormInt = qAbs( d->centerColorSelector * (1.0 - d->wheelWidthProportion)); d->wheelInnerRadius = d->centerColorSelector * (1.0 - d->wheelWidthProportion); d->triangleRadius = d->wheelInnerRadius * 0.9; d->triangleLength = 3.0 / sqrt(3.0) * d->triangleRadius; d->triangleHeight = d->triangleLength * sqrt(3.0) * 0.5; d->triangleTop = 0.5 * d->sizeColorSelector - d->triangleRadius; d->triangleBottom = d->triangleHeight + d->triangleTop; d->triangleHandleSize = 10.0; } void KoTriangleColorSelector::paintEvent( QPaintEvent * event ) { if( d->invalidTriangle ) { generateTriangle(); } Q_UNUSED(event); QPainter p(this); p.setRenderHint(QPainter::SmoothPixmapTransform); p.setRenderHint(QPainter::Antialiasing); QPointF pos(d->centerColorSelector, d->centerColorSelector); p.translate(QPointF( 0.5*width(), 0.5*height() ) ); // Draw the wheel p.drawPixmap( -pos, d->wheelPixmap ); // Draw the triangle p.save(); p.rotate( hue() + 150 ); p.drawPixmap( -pos , d->trianglePixmap ); // Draw selectors p.restore(); // Draw value,saturation selector // Compute coordinates { qreal vs_selector_ypos_ = value() / 255.0; qreal ls_ = (vs_selector_ypos_) * d->triangleLength; // length of the saturation on the triangle qreal vs_selector_xpos_ = ls_ * (saturation() / 255.0 - 0.5); // Draw it p.save(); p.setPen( QPen( Qt::white, 1.0) ); - QColor currentColor = d->displayRenderer->toQColor(realColor()); + QColor currentColor = d->displayRenderer->toQColor(getCurrentColor()); p.setBrush(currentColor); p.rotate( hue() + 150 ); p.drawEllipse( QRectF( -d->triangleHandleSize*0.5 + vs_selector_xpos_, -d->triangleHandleSize*0.5 - (d->centerColorSelector - d->triangleTop) + vs_selector_ypos_ * d->triangleHeight, d->triangleHandleSize , d->triangleHandleSize )); } p.restore(); // Draw Hue selector p.save(); p.setPen( QPen( Qt::white, 1.0) ); p.rotate( hue() - 90 ); qreal hueSelectorWidth_ = 0.8; qreal hueSelectorOffset_ = 0.5 *( 1.0 - hueSelectorWidth_) * d->wheelWidth; qreal hueSelectorSize_ = 0.8 * d->wheelWidth; p.drawRect( QRectF( -1.5, -d->centerColorSelector + hueSelectorOffset_, 3.0, hueSelectorSize_ )); p.restore(); p.end(); } // make sure to always use get/set functions when managing HSV properties( don't call directly like d->hue) // these settings get updated A LOT when the color picker is being used. You might get unexpected results int KoTriangleColorSelector::hue() const { return d->hue; } void KoTriangleColorSelector::setHue(int h) { // setRealColor() will give you -1 when saturation is 0 // ignore setting hue in this instance. otherwise it will mess up the hue ring if (h == -1) return; h = qBound(0, h, 359); d->hue = h; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } int KoTriangleColorSelector::value() const { return d->value; } void KoTriangleColorSelector::setValue(int v) { v = qBound(0, v, 255); d->value = v; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } int KoTriangleColorSelector::saturation() const { return d->saturation; } void KoTriangleColorSelector::setSaturation(int s) { s = qBound(0, s, 255); d->saturation = s; tellColorChanged(); d->invalidTriangle = true; d->updateTimer.start(); } void KoTriangleColorSelector::setHSV(int h, int s, int v) { d->invalidTriangle = (hue() != h); setHue(h); setValue(v); setSaturation(s); } -KoColor KoTriangleColorSelector::realColor() const +KoColor KoTriangleColorSelector::getCurrentColor() const { return d->displayRenderer->fromHsv(hue(), saturation(), value()); } -void KoTriangleColorSelector::setRealColor(const KoColor & color) +void KoTriangleColorSelector::slotSetColor(const KoColor & color) { - if ( realColor() == color) + if ( getCurrentColor() == color) return; //displayrenderer->getHsv is what sets the foreground color in the application if(d->updateAllowed) { int hueRef = hue(); int saturationRef = saturation(); int valueRef = value(); d->displayRenderer->getHsv(color, &hueRef, &saturationRef, &valueRef); setHSV(hueRef, saturationRef, valueRef); d->invalidTriangle = true; d->updateTimer.start(); } } QColor KoTriangleColorSelector::color() const { - return realColor().toQColor(); + return getCurrentColor().toQColor(); } void KoTriangleColorSelector::resizeEvent( QResizeEvent * event ) { QWidget::resizeEvent( event ); updateTriangleCircleParameters(); generateWheel(); d->invalidTriangle = true; } inline qreal pow2(qreal v) { return v*v; } void KoTriangleColorSelector::tellColorChanged() { d->updateAllowed = false; - emit(realColorChanged(realColor())); - emit(colorChanged(realColor().toQColor())); + emit(sigNewColor(getCurrentColor())); + emit(colorChanged(getCurrentColor().toQColor())); d->updateAllowed = true; } void KoTriangleColorSelector::generateTriangle() { QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32); // Length of triangle int hue_ = hue(); - + for(int y = 0; y < d->sizeColorSelector; ++y) { qreal ynormalize = ( d->triangleTop - y ) / ( d->triangleTop - d->triangleBottom ); qreal v = 255 * ynormalize; qreal ls_ = (ynormalize) * d->triangleLength; qreal startx_ = d->centerColorSelector - 0.5 * ls_; uint* data = reinterpret_cast(image.scanLine(y)); for(int x = 0; x < d->sizeColorSelector; ++x, ++data) { qreal s = 255 * (x - startx_) / ls_; if(v < -1.0 || v > 256.0 || s < -1.0 || s > 256.0 ) { *data = qRgba(0,0,0,0); } else { qreal va = 1.0, sa = 1.0; if( v < 0.0) { va = 1.0 + v; v = 0; } else if( v > 255.0 ) { va = 256.0 - v; v = 255; } if( s < 0.0) { sa = 1.0 + s; s = 0; } else if( s > 255.0 ) { sa = 256.0 - s; s = 255; } qreal coeff = va * sa; KoColor color = d->displayRenderer->fromHsv(hue_, s, v, int(coeff * 255.0)); QColor qcolor = d->displayRenderer->toQColor(color); *data = qcolor.rgba(); } } } - + d->trianglePixmap = QPixmap::fromImage(image); d->invalidTriangle = false; } void KoTriangleColorSelector::generateWheel() { QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32); for(int y = 0; y < d->sizeColorSelector; y++) { qreal yc = y - d->centerColorSelector; qreal y2 = pow2( yc ); for(int x = 0; x < d->sizeColorSelector; x++) { qreal xc = x - d->centerColorSelector; qreal norm = sqrt(pow2( xc ) + y2); if( norm <= d->wheelNormExt + 1.0 && norm >= d->wheelNormInt - 1.0 ) { qreal acoef = 1.0; if(norm > d->wheelNormExt ) acoef = (1.0 + d->wheelNormExt - norm); else if(norm < d->wheelNormInt ) acoef = (1.0 - d->wheelNormInt + norm); qreal angle = atan2(yc, xc); int h = (int)((180 * angle / M_PI) + 180) % 360; KoColor color = d->displayRenderer->fromHsv(h, 255, 255, int(acoef * 255.0)); QColor qcolor = d->displayRenderer->toQColor(color); image.setPixel(x,y, qcolor.rgba()); } else { image.setPixel(x,y, qRgba(0,0,0,0)); } } } d->wheelPixmap = QPixmap::fromImage(image); } void KoTriangleColorSelector::mouseReleaseEvent( QMouseEvent * event ) { if(event->button() == Qt::LeftButton) { selectColorAt( event->x(), event->y()); d->handle = NoHandle; } else { QWidget::mouseReleaseEvent( event ); } } void KoTriangleColorSelector::mousePressEvent( QMouseEvent * event ) { if(event->button() == Qt::LeftButton) { d->handle = NoHandle; selectColorAt( event->x(), event->y()); } else { QWidget::mousePressEvent( event ); } } void KoTriangleColorSelector::mouseMoveEvent( QMouseEvent * event ) { if(event->buttons() & Qt::LeftButton) { selectColorAt( event->x(), event->y(), false ); } else { QWidget::mouseMoveEvent( event); } } void KoTriangleColorSelector::selectColorAt(int _x, int _y, bool checkInWheel) { Q_UNUSED( checkInWheel ); - + if (d->lastX == _x && d->lastY == _y) { return; } d->lastX = _x; d->lastY = _y; - + qreal x = _x - 0.5*width(); qreal y = _y - 0.5*height(); // Check if the click is inside the wheel qreal norm = sqrt( x * x + y * y); if ( ( (norm < d->wheelNormExt) && (norm > d->wheelNormInt) && d->handle == NoHandle ) || d->handle == HueHandle ) { d->handle = HueHandle; setHue( (int)(atan2(y, x) * 180 / M_PI ) + 180); d->updateTimer.start(); } else { // Compute the s and v value, if they are in range, use them qreal rotation = -(hue() + 150) * M_PI / 180; qreal cr = cos(rotation); qreal sr = sin(rotation); qreal x1 = x * cr - y * sr; // <- now x1 gives the saturation qreal y1 = x * sr + y * cr; // <- now y1 gives the value y1 += d->wheelNormExt; qreal ynormalize = (d->triangleTop - y1 ) / ( d->triangleTop - d->triangleBottom ); if( (ynormalize >= 0.0 && ynormalize <= 1.0 ) || d->handle == ValueSaturationHandle) { d->handle = ValueSaturationHandle; qreal ls_ = (ynormalize) * d->triangleLength; // length of the saturation on the triangle qreal sat = ( x1 / ls_ + 0.5) ; if((sat >= 0.0 && sat <= 1.0) || d->handle == ValueSaturationHandle) { setHSV( hue(), sat * 255, ynormalize * 255); } } d->updateTimer.start(); } } void KoTriangleColorSelector::configurationChanged() { generateWheel(); d->invalidTriangle = true; update(); } diff --git a/libs/widgets/KoTriangleColorSelector.h b/libs/widgets/KoTriangleColorSelector.h index 31b3595b00..e164020d4c 100644 --- a/libs/widgets/KoTriangleColorSelector.h +++ b/libs/widgets/KoTriangleColorSelector.h @@ -1,73 +1,74 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 _KO_TRIANGLE_COLOR_SELECTOR_H_ #define _KO_TRIANGLE_COLOR_SELECTOR_H_ #include #include "kritawidgets_export.h" +#include + class KoColor; class KoColorDisplayRendererInterface; -class KRITAWIDGETS_EXPORT KoTriangleColorSelector : public QWidget { +class KRITAWIDGETS_EXPORT KoTriangleColorSelector : public KisColorSelectorInterface { Q_OBJECT public: explicit KoTriangleColorSelector(QWidget *parent); explicit KoTriangleColorSelector(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent); ~KoTriangleColorSelector(); protected: // events void paintEvent( QPaintEvent * event ); void resizeEvent( QResizeEvent * event ); void mouseReleaseEvent( QMouseEvent * event ); void mousePressEvent( QMouseEvent * event ); void mouseMoveEvent( QMouseEvent * event ); public: int hue() const; int value() const; int saturation() const; - KoColor realColor() const; + KoColor getCurrentColor() const; // please use realColor() instead! Q_DECL_DEPRECATED QColor color() const; public Q_SLOTS: void setHue(int h); void setValue(int v); void setSaturation(int s); void setHSV(int h, int s, int v); - void setRealColor(const KoColor& ); + void slotSetColor(const KoColor& ); Q_SIGNALS: void colorChanged(const QColor& ); - void realColorChanged(const KoColor& ); private Q_SLOTS: void configurationChanged(); private: void tellColorChanged(); void generateTriangle(); void generateWheel(); void updateTriangleCircleParameters(); void selectColorAt(int x, int y, bool checkInWheel = true); private: struct Private; Private* const d; }; #endif diff --git a/libs/widgets/KoZoomController.cpp b/libs/widgets/KoZoomController.cpp index 9ec9a9cbcf..38b39e23f0 100644 --- a/libs/widgets/KoZoomController.cpp +++ b/libs/widgets/KoZoomController.cpp @@ -1,266 +1,252 @@ /* This file is part of the KDE project * Copyright (C) 2007 C. Boemann * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2010 Boudewijn Rempt * Copyright (C) 2011 Arjen Hiemstra * * 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 #include #include #include #include #include #include #include void KoZoomController::Private::init(KoCanvasController *co, KoZoomHandler *zh, - KActionCollection *actionCollection, - bool createZoomShortcuts) + KActionCollection *actionCollection) { canvasController = co; fitMargin = co->margin(); zoomHandler = zh; connect(action, SIGNAL(zoomChanged(KoZoomMode::Mode, qreal)), parent, SLOT(setZoom(KoZoomMode::Mode, qreal))); connect(action, SIGNAL(aspectModeChanged(bool)), parent, SIGNAL(aspectModeChanged(bool))); connect(action, SIGNAL(zoomedToSelection()), parent, SIGNAL(zoomedToSelection())); connect(action, SIGNAL(zoomedToAll()), parent, SIGNAL(zoomedToAll())); actionCollection->addAction("view_zoom", action); - if (createZoomShortcuts) { - actionCollection->addAction(KStandardAction::ZoomIn, "zoom_in", action, SLOT(zoomIn())); - actionCollection->addAction(KStandardAction::ZoomOut, "zoom_out", action, SLOT(zoomOut())); - } - connect(canvasController->proxyObject, SIGNAL( sizeChanged(const QSize & ) ), parent, SLOT( setAvailableSize() ) ); connect(canvasController->proxyObject, SIGNAL( zoomRelative(const qreal, const QPointF& ) ), parent, SLOT( requestZoomRelative( const qreal, const QPointF& ) ) ); } KoZoomController::KoZoomController(KoCanvasController *co, KoZoomHandler *zh, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons, QObject *parent) : QObject(parent), d(new Private(this, specialButtons)) { - d->init(co, zh, actionCollection, true); -} - - -KoZoomController::KoZoomController(KoCanvasController *co, KoZoomHandler *zh, KActionCollection *actionCollection, bool createZoomShortcuts, KoZoomAction::SpecialButtons specialButtons, QObject *parent) - : QObject(parent), - d(new Private(this, specialButtons)) -{ - d->init(co, zh, actionCollection, createZoomShortcuts); + d->init(co, zh, actionCollection); } KoZoomController::~KoZoomController() { delete d; } KoZoomAction *KoZoomController::zoomAction() const { return d->action; } void KoZoomController::setZoomMode(KoZoomMode::Mode mode) { setZoom(mode, 1.0); } void KoZoomController::setPageSize(const QSizeF &pageSize) { if(d->pageSize == pageSize) return; d->pageSize = pageSize; if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_WIDTH) setZoom(KoZoomMode::ZOOM_WIDTH, 0); if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_PAGE) setZoom(KoZoomMode::ZOOM_PAGE, 0); } QSizeF KoZoomController::pageSize() const { return d->pageSize; } void KoZoomController::setTextMinMax(qreal min, qreal max) { if(d->textMinX == min && d->textMaxX == max) { return; } d->textMinX = min; d->textMaxX = max; if(d->zoomHandler->zoomMode() == KoZoomMode::ZOOM_TEXT) setZoom(KoZoomMode::ZOOM_TEXT, 0); } void KoZoomController::setDocumentSize(const QSizeF &documentSize, bool recalculateCenter) { d->documentSize = documentSize; d->canvasController->updateDocumentSize(documentToViewport(d->documentSize), recalculateCenter); // Finally ask the canvasController to recenter d->canvasController->recenterPreferred(); } QSizeF KoZoomController::documentSize() const { return d->documentSize; } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom) { setZoom(mode, zoom, d->canvasController->preferredCenter()); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint) { setZoom(mode, zoom, d->zoomHandler->resolutionX(), d->zoomHandler->resolutionY(), stillPoint); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY) { setZoom(mode, zoom, resolutionX, resolutionY, d->canvasController->preferredCenter()); } void KoZoomController::setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint) { if (d->zoomHandler->zoomMode() == mode && qFuzzyCompare(d->zoomHandler->zoom(), zoom) && qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) && qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) { return; // no change } qreal oldEffectiveZoom = d->action->effectiveZoom(); QSize oldPageViewportSize = documentToViewport(d->pageSize); QSize oldTextViewportSize = documentToViewport(QSizeF(d->textMaxX-d->textMinX, 1)); qreal yfixAlignTop = d->canvasController->viewportSize().height(); if(!qFuzzyCompare(d->zoomHandler->resolutionX(), resolutionX) || !qFuzzyCompare(d->zoomHandler->resolutionY(), resolutionY)) { d->zoomHandler->setResolution(resolutionX, resolutionY); } if(mode == KoZoomMode::ZOOM_CONSTANT) { if(zoom == 0.0) return; d->action->setZoom(zoom); } else if(mode == KoZoomMode::ZOOM_WIDTH) { zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin) / (oldPageViewportSize.width() / d->zoomHandler->zoom()); d->action->setSelectedZoomMode(mode); d->action->setEffectiveZoom(zoom); } else if(mode == KoZoomMode::ZOOM_PAGE) { zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin) / (oldPageViewportSize.width() / d->zoomHandler->zoom()); zoom = qMin(zoom, (d->canvasController->viewportSize().height() - 2 * d->fitMargin) / (oldPageViewportSize.height() / d->zoomHandler->zoom())); d->action->setSelectedZoomMode(mode); d->action->setEffectiveZoom(zoom); } else if (mode == KoZoomMode::ZOOM_TEXT) { zoom = (d->canvasController->viewportSize().width() - 2 * d->fitMargin) / (oldTextViewportSize.width() / d->zoomHandler->zoom()); d->action->setSelectedZoomMode(mode); d->action->setEffectiveZoom(zoom); } d->zoomHandler->setZoomMode(mode); d->zoomHandler->setZoom(d->action->effectiveZoom()); #ifdef DEBUG if(! d->documentSize.isValid()) warnWidgets << "Setting zoom while there is no document size set, this will fail"; else if (d->pageSize.width() > d->documentSize.width() || d->pageSize.height() > d->documentSize.height()) warnWidgets << "ZoomController; Your page size is larger than your document size (" << d->pageSize << " > " << d->documentSize << ")\n"; #endif QSize documentViewportSize = documentToViewport(d->documentSize); // Tell the canvasController that the zoom has changed // Actually canvasController doesn't know about zoom, but the document in pixels // has changed as a result of the zoom change // To allow listeners of offset changes to react on the real new offset and not on the // intermediate offsets, we block the signals here, and emit by ourselves later. d->canvasController->proxyObject->blockSignals(true); d->canvasController->updateDocumentSize(documentViewportSize, true); d->canvasController->proxyObject->blockSignals(false); // Finally ask the canvasController to recenter if (d->canvasController->canvasMode() == KoCanvasController::Infinite) { QPointF documentCenter; if (mode == KoZoomMode::ZOOM_WIDTH || mode == KoZoomMode::ZOOM_PAGE) { documentCenter = QRectF(QPointF(), documentViewportSize).center(); } else { qreal zoomCoeff = d->action->effectiveZoom() / oldEffectiveZoom; QPointF oldCenter = d->canvasController->preferredCenter(); documentCenter = stillPoint * zoomCoeff - (stillPoint - 1.0 / zoomCoeff * oldCenter); } d->canvasController->setPreferredCenter(documentCenter); } else if (mode == KoZoomMode::ZOOM_TEXT) { QPointF documentCenter = d->canvasController->preferredCenter(); yfixAlignTop -= d->canvasController->viewportSize().height(); documentCenter.setX(d->zoomHandler->documentToViewX(d->textMinX + d->textMaxX) * 0.5); documentCenter.setY(documentCenter.y() - yfixAlignTop); d->canvasController->setPreferredCenter(documentCenter); } else { if (d->canvasController->canvasMode() == KoCanvasController::AlignTop) { QPointF documentCenter = d->canvasController->preferredCenter(); documentCenter.setX(0.0); d->canvasController->setPreferredCenter(documentCenter); } else { d->canvasController->recenterPreferred(); } } // now that we have the final offset, let's emit some signals //d->canvasController->proxyObject->emitCanvasOffsetXChanged(d->canvasController->canvasOffsetX()); //d->canvasController->proxyObject->emitCanvasOffsetYChanged(d->canvasController->canvasOffsetY()); emit zoomChanged(mode, d->action->effectiveZoom()); } QSize KoZoomController::documentToViewport(const QSizeF &size) { return d->zoomHandler->documentToView(size).toSize(); } void KoZoomController::setAspectMode(bool status) { if (d->action) { d->action->setAspectMode(status); } } //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/widgets/KoZoomController.h b/libs/widgets/KoZoomController.h index 6bb745e184..12c1abde4b 100644 --- a/libs/widgets/KoZoomController.h +++ b/libs/widgets/KoZoomController.h @@ -1,225 +1,207 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007,2012 C. Boemann * Copyright (C) 2007 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOZOOMCONTROLLER_H #define KOZOOMCONTROLLER_H #include "KoZoomAction.h" #include "kritawidgets_export.h" #include #include #include class KoCanvasController; class KoZoomAction; class KoZoomHandler; class KActionCollection; class QSize; /** * This controller class handles zoom levels for any canvas. * * For each KoCanvasController you should have one instance of this * class to go with it. This class then creates a KoZoomAction and * basically handles all zooming for you. * * All you need to do is connect to the setDocumentSize() slot and * keep the controller up-to-date if your on-screen document ever * changes (note that this is in document units, so this is a zoom * independent size). * * If you choose to have zoom modes of 'page' and 'width' you are * required to set the page size using the setPageSize() method. * * Additionally you can connect to the zoomChanged() signal if you * want to store the latest zoom level and mode, for example to * restore the last used one at next restart. * * The specialAspectMode toggle is only a UI element. It does nothing * except emit the aspectModeChanged signal. * */ class KRITAWIDGETS_EXPORT KoZoomController : public QObject { Q_OBJECT public: /** * Constructor. Create one per canvasController. The zoomAction is created in the constructor and will * be available to the passed actionCollection for usage by XMLGui. * @param controller the canvasController * @param zoomHandler the zoom handler (viewconverter with setter methods) * @param actionCollection the action collection where the KoZoomAction is added to * @param specialButtons controls which special buttons to show */ KoZoomController(KoCanvasController *controller, KoZoomHandler *zoomHandler, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons = 0, QObject *parent = 0); - /** - * A special override for for creation of a zoom controller - * without automatic generation of standard zoom in and zoom out - * actions. The caller is supposed create and connect the - * corresponding actions himself. - * - * @param createZoomShortcuts shows whether the zoom actions should be - * created or not. - * - * Used in KisZoomController. - */ - KoZoomController(KoCanvasController *controller, - KoZoomHandler *zoomHandler, - KActionCollection *actionCollection, - bool createZoomShortcuts, - KoZoomAction::SpecialButtons specialButtons = 0, - QObject *parent = 0); - /// destructor ~KoZoomController(); /// returns the zoomAction that is maintained by this controller KoZoomAction *zoomAction() const; /** * Alter the current zoom mode which updates the Gui. * @param mode the new mode that will be used to auto-calculate a new zoom-level if needed. */ void setZoomMode(KoZoomMode::Mode mode); /** * Set the resolution, zoom, the zoom mode for this zoom Controller. * Typically for use just after construction to restore the * persistent data. * * @param mode new zoom mode for the canvas * @param zoom (for ZOOM_CONSTANT zoom mode only) new zoom value for * the canvas * @param resolutionX new X resolution for the document * @param resolutionY new Y resolution for the document * @param stillPoint (for ZOOM_CONSTANT zoom mode only) the point * which will not change its position in widget * during the zooming. It is measured in view * coordinate system *before* zoom. */ void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY, const QPointF &stillPoint); /** * Convenience function that changes resolution with * keeping the centering unchanged */ void setZoom(KoZoomMode::Mode mode, qreal zoom, qreal resolutionX, qreal resolutionY); /** * Convenience function that does not touch the resolution of the * document */ void setZoom(KoZoomMode::Mode mode, qreal zoom, const QPointF &stillPoint); /** * Convenience function with @p center always set to the current * center point of the canvas */ void setZoom(KoZoomMode::Mode mode, qreal zoom); /** * Set Aspect Mode button status and begin a chain of signals */ void setAspectMode(bool status); public Q_SLOTS: /** * Set the size of the current page in document coordinates which allows zoom modes that use the pageSize * to update. * @param pageSize the new page size in points */ void setPageSize(const QSizeF &pageSize); /** * Returns the size of the current page in document coordinates * @returns the page size in points */ QSizeF pageSize() const; /** * Set the dimensions of where text can appear which allows zoom modes that use the text * to update. * @param min the minimum x value (in document coordinates) where text can appear * @param max the maximum x value (in document coordinates) where text can appear */ void setTextMinMax(qreal min, qreal max); /** * Set the size of the whole document currently being shown on the canvas. * The document size will be used together with the current zoom level to calculate the size of the * canvas in the canvasController. * @param documentSize the new document size in points * @param recalculateCenter tells canvas controller not to touch * preferredCenterFraction */ void setDocumentSize(const QSizeF &documentSize, bool recalculateCenter = false); /** * Returns the size of the whole document currently being shown on the canvas. * @returns the document size in points */ QSizeF documentSize() const; Q_SIGNALS: /** * This signal is emitted whenever either the zoommode or the zoom level is changed by the user. * the application can use the emitted data for persistency purposes. */ void zoomChanged (KoZoomMode::Mode mode, qreal zoom); /** * emitted when the special aspect mode toggle changes. * @see KoZoomAction::aspectModeChanged() */ void aspectModeChanged (bool aspectModeActivated); /** * Signal is triggered when the user clicks the zoom to selection button. * Nothing else happens except that this signal is emitted. */ void zoomedToSelection(); /** * Signal is triggered when the user clicks the zoom to all button. * Nothing else happens except that this signal is emitted. */ void zoomedToAll(); protected: virtual QSize documentToViewport(const QSizeF &size); private: Q_PRIVATE_SLOT(d, void setAvailableSize()) Q_PRIVATE_SLOT(d, void requestZoomRelative(const qreal, const QPointF&)) Q_PRIVATE_SLOT(d, void setZoom(KoZoomMode::Mode, qreal)) Q_DISABLE_COPY( KoZoomController ) class Private; Private * const d; }; #endif diff --git a/libs/widgets/KoZoomController_p.h b/libs/widgets/KoZoomController_p.h index 088a7bcef4..7b644f454d 100644 --- a/libs/widgets/KoZoomController_p.h +++ b/libs/widgets/KoZoomController_p.h @@ -1,86 +1,85 @@ /* This file is part of the KDE project * Copyright (C) 2007 C. Boemann * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2010 Boudewijn Rempt * Copyright (C) 2011 Arjen Hiemstra * * 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 KoZoomController_p_h #define KoZoomController_p_h #include #include #include #include #include #include class Q_DECL_HIDDEN KoZoomController::Private { public: Private(KoZoomController *p, KoZoomAction::SpecialButtons specialButtons) : canvasController(0), zoomHandler(0), action(0), textMinX(1), textMaxX(600), fitMargin(0), parent(p) { action = new KoZoomAction(KoZoomMode::ZOOM_WIDTH | KoZoomMode::ZOOM_PAGE, i18n("Zoom"), p); action->setSpecialButtons(specialButtons); } ~Private() { } /// so we know when the canvasController changes size void setAvailableSize() { if(zoomHandler->zoomMode() == KoZoomMode::ZOOM_WIDTH) setZoom(KoZoomMode::ZOOM_WIDTH, -1); if(zoomHandler->zoomMode() == KoZoomMode::ZOOM_PAGE) setZoom(KoZoomMode::ZOOM_PAGE, -1); if(zoomHandler->zoomMode() == KoZoomMode::ZOOM_TEXT) setZoom(KoZoomMode::ZOOM_TEXT, -1); } /// when the canvas controller wants us to change zoom void requestZoomRelative(const qreal factor, const QPointF& stillPoint) { parent->setZoom(KoZoomMode::ZOOM_CONSTANT, factor * zoomHandler->zoom(), stillPoint); } void setZoom(KoZoomMode::Mode mode, qreal zoom) { parent->setZoom(mode, zoom); } void init(KoCanvasController *co, KoZoomHandler *zh, - KActionCollection *actionCollection, - bool createZoomShortcuts); + KActionCollection *actionCollection); KoCanvasController *canvasController; KoZoomHandler *zoomHandler; KoZoomAction *action; QSizeF pageSize; qreal textMinX; qreal textMaxX; QSizeF documentSize; int fitMargin; KoZoomController *parent; }; #endif diff --git a/libs/widgetutils/CMakeLists.txt b/libs/widgetutils/CMakeLists.txt index 182db0ab80..ede32a1558 100644 --- a/libs/widgetutils/CMakeLists.txt +++ b/libs/widgetutils/CMakeLists.txt @@ -1,127 +1,128 @@ add_subdirectory(tests) configure_file(xmlgui/config-xmlgui.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-xmlgui.h ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/config) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/xmlgui) set(kritawidgetutils_LIB_SRCS WidgetUtilsDebug.cpp kis_icon_utils.cpp kis_action_registry.cpp + KisActionsSnapshot.cpp KoGroupButton.cpp KoProgressBar.cpp KoProgressUpdater.cpp KoUpdater.cpp KoUpdaterPrivate_p.cpp KoProperties.cpp KoFileDialog.cpp KoResourcePaths.cpp kis_num_parser.cpp config/kcolorscheme.cpp config/kcolorschememanager.cpp config/khelpclient.cpp config/klanguagebutton.cpp config/krecentfilesaction.cpp config/kstandardaction.cpp xmlgui/KisShortcutsEditorItem.cpp xmlgui/KisShortcutEditWidget.cpp xmlgui/KisShortcutsEditorDelegate.cpp xmlgui/KisShortcutsDialog.cpp xmlgui/KisShortcutsDialog_p.cpp xmlgui/KisShortcutsEditor.cpp xmlgui/KisShortcutsEditor_p.cpp xmlgui/kshortcutschemeseditor.cpp xmlgui/kshortcutschemeshelper.cpp xmlgui/kaboutkdedialog_p.cpp xmlgui/kactioncategory.cpp xmlgui/kactioncollection.cpp xmlgui/kactionconflictdetector.cpp xmlgui/kbugreport.cpp xmlgui/kcheckaccelerators.cpp xmlgui/kedittoolbar.cpp xmlgui/kgesture.cpp xmlgui/kgesturemap.cpp xmlgui/khelpmenu.cpp xmlgui/kkeysequencewidget.cpp xmlgui/kmainwindow.cpp xmlgui/kmenumenuhandler_p.cpp xmlgui/kshortcutwidget.cpp xmlgui/kswitchlanguagedialog_p.cpp xmlgui/ktoggletoolbaraction.cpp xmlgui/ktoolbar.cpp xmlgui/ktoolbarhandler.cpp xmlgui/kundoactions.cpp xmlgui/kxmlguibuilder.cpp xmlgui/kxmlguiclient.cpp xmlgui/kxmlguifactory.cpp xmlgui/kxmlguifactory_p.cpp xmlgui/kxmlguiversionhandler.cpp xmlgui/kxmlguiwindow.cpp ) if (HAVE_DBUS) set(kritawidgetutils_LIB_SRCS ${kritawidgetutils_LIB_SRCS} xmlgui/kmainwindowiface.cpp ) endif() ki18n_wrap_ui(kritawidgetutils_LIB_SRCS xmlgui/KisShortcutsDialog.ui xmlgui/kshortcutwidget.ui ) qt5_add_resources(kritawidgetutils_LIB_SRCS xmlgui/kxmlgui.qrc) add_library(kritawidgetutils SHARED ${kritawidgetutils_LIB_SRCS}) target_include_directories(kritawidgetutils PUBLIC $ $ ) generate_export_header(kritawidgetutils BASE_NAME kritawidgetutils) if (HAVE_DBUS) set (KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} Qt5::DBus) endif () if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) set(KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} ${FOUNDATION_LIBRARY}) endif () target_link_libraries(kritawidgetutils PUBLIC Qt5::Widgets Qt5::Gui Qt5::Xml Qt5::Core KF5::ItemViews kritaglobal PRIVATE Qt5::PrintSupport KF5::I18n KF5::ConfigCore KF5::CoreAddons KF5::ConfigGui KF5::GuiAddons KF5::WidgetsAddons KF5::WindowSystem kritaplugin ${KRITA_WIDGET_UTILS_EXTRA_LIBS} ) set_target_properties(kritawidgetutils PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritawidgetutils ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/widgetutils/KisActionsSnapshot.cpp b/libs/widgetutils/KisActionsSnapshot.cpp new file mode 100644 index 0000000000..f77b9d36ff --- /dev/null +++ b/libs/widgetutils/KisActionsSnapshot.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016 Dmitry Kazakov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "KisActionsSnapshot.h" + +#include "kis_action_registry.h" +#include "./kactioncollection.h" + +#include "kis_debug.h" + +//#define ACTIONS_CHECKSUM_SANITY_CHECK + + +struct KisActionsSnapshot::Private +{ + QMap actionCollections; + + ~Private() { + qDeleteAll(actionCollections); + qDeleteAll(fakeActions); + } + + QSet nonRegisteredShortcuts; + QVector fakeActions; +}; + +KisActionsSnapshot::KisActionsSnapshot() + : m_d(new Private) +{ + m_d->nonRegisteredShortcuts = + QSet::fromList( + KisActionRegistry::instance()->registeredShortcutIds()); +} + +KisActionsSnapshot::~KisActionsSnapshot() +{ +} + +void KisActionsSnapshot::addAction(const QString &name, QAction *action) +{ + m_d->nonRegisteredShortcuts.remove(name); + KisActionRegistry::ActionCategory cat = KisActionRegistry::instance()->fetchActionCategory(name); + + if (!cat.isValid()) { + warnKrita << "WARNING: Uncategorized action" << name << "Dropping..."; + return; + } + +#ifdef ACTIONS_CHECKSUM_SANITY_CHECK + if (!KisActionRegistry::instance()->sanityCheckPropertized(action->objectName())) { + warnKrita << "WARNING: action" << name << "was not propertized!" << ppVar(action->property("isShortcutConfigurable").toBool()); + } +#endif /* ACTIONS_CHECKSUM_SANITY_CHECK */ + + KActionCollection *collection = m_d->actionCollections[cat.componentName]; + + if (!collection) { + collection = new KActionCollection(0, cat.componentName); + m_d->actionCollections.insert(cat.componentName, collection); + } + + collection->addCategorizedAction(name, action, cat.categoryName); +} + +QMap KisActionsSnapshot::actionCollections() +{ + /** + * A small heruistics to show warnings only when unknown shortcuts arppear + * in the non-registered list + */ + if (m_d->nonRegisteredShortcuts.size() > 4 && + m_d->nonRegisteredShortcuts.size() < 160) { + + warnKrita << "WARNING: The following shortcuts are not registeren in the collection, " + "they might have wrong shortcuts in the end:"; + Q_FOREACH (const QString &str, m_d->nonRegisteredShortcuts) { + warnKrita << str; + } + warnKrita << "=== end ==="; + } + + // try to workaround non-registered shortcuts by faking them manually + Q_FOREACH (const QString &str, m_d->nonRegisteredShortcuts) { + QAction *action = KisActionRegistry::instance()->makeQAction(str, 0); + m_d->fakeActions << action; + addAction(action->objectName(), action); + } + + return m_d->actionCollections; +} + diff --git a/libs/ui/dialogs/kis_delayed_save_dialog.h b/libs/widgetutils/KisActionsSnapshot.h similarity index 50% copy from libs/ui/dialogs/kis_delayed_save_dialog.h copy to libs/widgetutils/KisActionsSnapshot.h index 40e742cd00..6dcad950ca 100644 --- a/libs/ui/dialogs/kis_delayed_save_dialog.h +++ b/libs/widgetutils/KisActionsSnapshot.h @@ -1,52 +1,59 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef KIS_DELAYED_SAVE_DIALOG_H -#define KIS_DELAYED_SAVE_DIALOG_H +#ifndef KISACTIONSSNAPSHOT_H +#define KISACTIONSSNAPSHOT_H + +#include -#include #include -#include "kis_types.h" +#include -namespace Ui { -class KisDelayedSaveDialog; -} +class QAction; +class KActionCollection; -class KisDelayedSaveDialog : public QDialog -{ - Q_OBJECT +class KRITAWIDGETUTILS_EXPORT KisActionsSnapshot +{ public: - explicit KisDelayedSaveDialog(KisImageSP image, QWidget *parent = 0); - ~KisDelayedSaveDialog(); - - void blockIfImageIsBusy(); - -private Q_SLOTS: - void slotTimerTimeout(); - void slotCancelRequested(); - -private: - Ui::KisDelayedSaveDialog *ui; + KisActionsSnapshot(); + ~KisActionsSnapshot(); + + /** + * @brief registers the action in the snapshot and sorts it into a proper + * category. The action is *not* owned by the snapshot. + * + * @param name id string of the action + * @param action the action itself + */ + void addAction(const QString &name, QAction *action); + + /** + * Returns all action collections of the current snapshot + * + * WARNING: the collections are owned by the shapshot! Don't destroy + * the snapshot before you are done with the collections! + */ + QMap actionCollections(); private: struct Private; const QScopedPointer m_d; }; -#endif // KIS_DELAYED_SAVE_DIALOG_H +#endif // KISACTIONSSNAPSHOT_H diff --git a/libs/widgetutils/KoFileDialog.cpp b/libs/widgetutils/KoFileDialog.cpp index 8654d7e7ad..85538d4329 100644 --- a/libs/widgetutils/KoFileDialog.cpp +++ b/libs/widgetutils/KoFileDialog.cpp @@ -1,399 +1,397 @@ /* This file is part of the KDE project Copyright (C) 2013 - 2014 Yue Liu 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 "KoFileDialog.h" #include #include #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KoFileDialog::Private { public: Private(QWidget *parent_, KoFileDialog::DialogType dialogType_, const QString caption_, const QString defaultDir_, const QString dialogName_) : parent(parent_) , type(dialogType_) , dialogName(dialogName_) , caption(caption_) , defaultDirectory(defaultDir_) , filterList(QStringList()) , defaultFilter(QString()) , swapExtensionOrder(false) { } ~Private() { } QWidget *parent; KoFileDialog::DialogType type; QString dialogName; QString caption; QString defaultDirectory; QStringList filterList; QString defaultFilter; QScopedPointer fileDialog; QString mimeType; bool swapExtensionOrder; }; KoFileDialog::KoFileDialog(QWidget *parent, KoFileDialog::DialogType type, const QString &dialogName) : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName)) { } KoFileDialog::~KoFileDialog() { delete d; } void KoFileDialog::setCaption(const QString &caption) { d->caption = caption; } void KoFileDialog::setDefaultDir(const QString &defaultDir) { - QFileInfo f(defaultDir); - d->defaultDirectory = f.absoluteFilePath(); -} - -void KoFileDialog::setOverrideDir(const QString &overrideDir) -{ - d->defaultDirectory = overrideDir; + //qDebug() << d->defaultDirectory << d->dialogName << getUsedDir(d->dialogName); + if (d->defaultDirectory.isEmpty()) { + QFileInfo f(defaultDir); + d->defaultDirectory = f.absoluteFilePath(); + } } void KoFileDialog::setImageFilters() { QStringList imageFilters; // add filters for all formats supported by QImage Q_FOREACH (const QByteArray &format, QImageReader::supportedImageFormats()) { imageFilters << QLatin1String("image/") + format; } setMimeTypeFilters(imageFilters); } void KoFileDialog::setMimeTypeFilters(const QStringList &filterList, QString defaultFilter) { d->filterList = getFilterStringListFromMime(filterList, true); if (!defaultFilter.isEmpty()) { QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultFilter, false); if (defaultFilters.size() > 0) { defaultFilter = defaultFilters.first(); } } d->defaultFilter = defaultFilter; } QString KoFileDialog::selectedNameFilter() const { return d->fileDialog->selectedNameFilter(); } QString KoFileDialog::selectedMimeType() const { return d->mimeType; } void KoFileDialog::createFileDialog() { //qDebug() << "createFileDialog. Parent:" << d->parent << "Caption:" << d->caption << "Default directory:" << d->defaultDirectory << "Default iflter:" << d->defaultFilter; d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory)); bool dontUseNative = false; #ifdef Q_OS_UNIX if (qgetenv("XDG_CURRENT_DESKTOP") != "KDE") { dontUseNative = true; } #endif d->fileDialog->setOption(QFileDialog::DontUseNativeDialog, dontUseNative); d->fileDialog->setOption(QFileDialog::DontConfirmOverwrite, false); d->fileDialog->setOption(QFileDialog::HideNameFilterDetails, true); if (d->type == SaveFile) { d->fileDialog->setAcceptMode(QFileDialog::AcceptSave); d->fileDialog->setFileMode(QFileDialog::AnyFile); } else { // open / import d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen); if (d->type == ImportDirectory || d->type == OpenDirectory){ d->fileDialog->setFileMode(QFileDialog::Directory); d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true); } else { // open / import file(s) if (d->type == OpenFile || d->type == ImportFile) { d->fileDialog->setFileMode(QFileDialog::ExistingFile); } else { // files d->fileDialog->setFileMode(QFileDialog::ExistingFiles); } } } d->fileDialog->setNameFilters(d->filterList); if (!QFileInfo(d->defaultDirectory).isDir()) { //qDebug() << "Finding the right mimetype for the given file" << d->defaultDirectory; QString mime = KisMimeDatabase::mimeTypeForFile(d->defaultDirectory); QString description = KisMimeDatabase::descriptionForMimeType(mime); Q_FOREACH(const QString &filter, d->filterList) { //qDebug() << "\tConsidering" << filter; if (filter.startsWith(description)) { d->fileDialog->selectNameFilter(filter); break; } } } else if (!d->defaultFilter.isEmpty()) { d->fileDialog->selectNameFilter(d->defaultFilter); } if (d->type == ImportDirectory || d->type == ImportFile || d->type == ImportFiles || d->type == SaveFile) { d->fileDialog->setWindowModality(Qt::WindowModal); } } QString KoFileDialog::filename() { QString url; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { url = d->fileDialog->selectedFiles().first(); } if (!url.isEmpty()) { if (d->type == SaveFile && QFileInfo(url).suffix().isEmpty()) { QString selectedFilter; // index 0 is all supported; if that is chosen, saveDocument will automatically make it .kra for (int i = 1; i < d->filterList.size(); ++i) { if (d->filterList[i].startsWith(d->fileDialog->selectedNameFilter())) { selectedFilter = d->filterList[i]; break; } } int start = selectedFilter.indexOf("*.") + 1; int end = selectedFilter.indexOf(" ", start); int n = end - start; QString extension = selectedFilter.mid(start, n); if (!(extension.contains(".") || url.endsWith("."))) { extension = "." + extension; } url = url + extension; } d->mimeType = KisMimeDatabase::mimeTypeForFile(url); saveUsedDir(url, d->dialogName); } return url; } QStringList KoFileDialog::filenames() { QStringList urls; createFileDialog(); if (d->fileDialog->exec() == QDialog::Accepted) { urls = d->fileDialog->selectedFiles(); } if (urls.size() > 0) { saveUsedDir(urls.first(), d->dialogName); } return urls; } QStringList KoFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList) { Q_ASSERT(mimeList); QStringList filters; QString description; if (nameFilter.contains("(")) { description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed(); } QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts ); entries.sort(); Q_FOREACH (QString entry, entries) { entry = entry.remove("*"); entry = entry.remove(")"); QString mimeType = KisMimeDatabase::mimeTypeForSuffix(entry); if (mimeType != "application/octet-stream") { if (!mimeList->contains(mimeType)) { mimeList->append(mimeType); filters.append(KisMimeDatabase::descriptionForMimeType(mimeType) + " ( *" + entry + " )"); } } else { filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )"); } } return filters; } const QStringList KoFileDialog::getFilterStringListFromMime(const QStringList &_mimeList, bool withAllSupportedEntry) { QStringList mimeSeen; // 1 QString allSupported; // 2 QString kritaNative; // 3 QString ora; QStringList ret; QStringList mimeList = _mimeList; mimeList.sort(); Q_FOREACH(const QString &mimeType, mimeList) { if (!mimeSeen.contains(mimeType)) { QString description = KisMimeDatabase::descriptionForMimeType(mimeType); if (description.isEmpty() && !mimeType.isEmpty()) { description = mimeType.split("/")[1]; if (description.startsWith("x-")) { description = description.remove(0, 2); } } QString oneFilter; QStringList patterns = KisMimeDatabase::suffixesForMimeType(mimeType); QStringList globPatterns; Q_FOREACH(const QString &pattern, patterns) { if (pattern.startsWith(".")) { globPatterns << "*" + pattern; } else if (pattern.startsWith("*.")) { globPatterns << pattern; } else { globPatterns << "*." + pattern; } } Q_FOREACH(const QString &glob, globPatterns) { if (d->swapExtensionOrder) { oneFilter.prepend(glob + " "); if (withAllSupportedEntry) { allSupported.prepend(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.prepend(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.prepend(glob.toUpper() + " "); } } #endif } else { oneFilter.append(glob + " "); if (withAllSupportedEntry) { allSupported.append(glob + " "); } #ifdef Q_OS_LINUX if (qgetenv("XDG_CURRENT_DESKTOP") == "GNOME") { oneFilter.append(glob.toUpper() + " "); if (withAllSupportedEntry) { allSupported.append(glob.toUpper() + " "); } } #endif } } Q_ASSERT(!description.isEmpty()); oneFilter = description + " ( " + oneFilter + ")"; if (mimeType == "application/x-krita") { kritaNative = oneFilter; continue; } if (mimeType == "image/openraster") { ora = oneFilter; continue; } else { ret << oneFilter; } mimeSeen << mimeType; } } ret.sort(); ret.removeDuplicates(); if (!ora.isEmpty()) ret.prepend(ora); if (!kritaNative.isEmpty()) ret.prepend(kritaNative); if (!allSupported.isEmpty()) ret.prepend(i18n("All supported formats") + " ( " + allSupported + (")")); return ret; } QString KoFileDialog::getUsedDir(const QString &dialogName) { if (dialogName.isEmpty()) return ""; KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); - QString dir = group.readEntry(dialogName); + QString dir = group.readEntry(dialogName, ""); return dir; } void KoFileDialog::saveUsedDir(const QString &fileName, const QString &dialogName) { if (dialogName.isEmpty()) return; QFileInfo fileInfo(fileName); KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); group.writeEntry(dialogName, fileInfo.absolutePath()); } diff --git a/libs/widgetutils/KoFileDialog.h b/libs/widgetutils/KoFileDialog.h index 234036b83e..c2e6607ebe 100644 --- a/libs/widgetutils/KoFileDialog.h +++ b/libs/widgetutils/KoFileDialog.h @@ -1,132 +1,126 @@ /* This file is part of the KDE project Copyright (C) 2013 - 2014 Yue Liu 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 KOFILEDIALOG_H #define KOFILEDIALOG_H #include "kritawidgetutils_export.h" #include #include #include #include /** * Wrapper around QFileDialog providing native file dialogs * on KDE/Gnome/Windows/OSX/etc. */ class KRITAWIDGETUTILS_EXPORT KoFileDialog : public QObject { Q_OBJECT public: enum DialogType { OpenFile, OpenFiles, OpenDirectory, ImportFile, ImportFiles, ImportDirectory, SaveFile }; /** * @brief constructor * @param parent The parent of the file dialog * @param dialogType usage of the file dialog * @param dialogName the name for the file dialog. This will be used to open * the filedialog in the last open location, instead the specified directory. * * @return The name of the entry user selected in the file dialog * */ KoFileDialog(QWidget *parent, KoFileDialog::DialogType type, const QString &dialogName); ~KoFileDialog(); void setCaption(const QString &caption); /** * @brief setDefaultDir set the default directory to defaultDir * * @param defaultDir a path to a file or directory */ void setDefaultDir(const QString &defaultDir); - /** - * @brief setOverrideDir override both the default dir and the saved dir found by dialogName - * @param overrideDir a path to a file or directory - */ - void setOverrideDir(const QString &overrideDir); - /** * @brief setImageFilters sets the name filters for the file dialog to all * image formats Qt's QImageReader supports. */ void setImageFilters(); void setMimeTypeFilters(const QStringList &filterList, QString defaultFilter = QString()); /// Get the file names the user selected in the file dialog QStringList filenames(); /// Get the file name the user selected in the file dialog QString filename(); /** * @brief selectedNameFilter returns the name filter the user selected, either * directory or by clicking on it. * @return */ QString selectedNameFilter() const; QString selectedMimeType() const; private: /** * @brief splitNameFilter take a single line of a QDialog name filter and split it * into several lines. This is needed because a single line name filter can contain * more than one mimetype, making it impossible to figure out the correct extension. * * The methods takes care of some duplicated extensions, like jpeg and jpg. * @param nameFilter the namefilter to be split * @param mimeList a pointer to the list with mimes that shouldn't be added. * @return a stringlist of all name filters. */ static QStringList splitNameFilter(const QString &nameFilter, QStringList *mimeList); void createFileDialog(); QString getUsedDir(const QString &dialogName); void saveUsedDir(const QString &fileName, const QString &dialogName); const QStringList getFilterStringListFromMime(const QStringList &mimeList, bool withAllSupportedEntry = false); class Private; Private * const d; }; #endif /* KOFILEDIALOG_H */ diff --git a/libs/widgetutils/kis_action_registry.cpp b/libs/widgetutils/kis_action_registry.cpp index c6c8a7356f..a7cae46a10 100644 --- a/libs/widgetutils/kis_action_registry.cpp +++ b/libs/widgetutils/kis_action_registry.cpp @@ -1,431 +1,410 @@ /* * Copyright (c) 2015 Michael Abrahams * * 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "KoResourcePaths.h" #include "kis_icon_utils.h" -#include "kactioncollection.h" -#include "kactioncategory.h" #include "kis_action_registry.h" #include "kshortcutschemeshelper_p.h" namespace { /** * We associate several pieces of information with each shortcut. The first * piece of information is a QDomElement, containing the raw data from the * .action XML file. The second and third are QKeySequences, the first of * which is the default shortcut, the last of which is any custom shortcut. * The last two are the KActionCollection and KActionCategory used to * organize the shortcut editor. */ struct ActionInfoItem { QDomElement xmlData; - QList defaultShortcuts; - QList customShortcuts; + QString collectionName; QString categoryName; + + inline QList defaultShortcuts() const { + return m_defaultShortcuts; + } + + inline void setDefaultShortcuts(const QList &value) { + m_defaultShortcuts = value; + } + + inline QList customShortcuts() const { + return m_customShortcuts; + } + + inline void setCustomShortcuts(const QList &value, bool explicitlyReset) { + m_customShortcuts = value; + m_explicitlyReset = explicitlyReset; + } + + inline QList effectiveShortcuts() const { + return m_customShortcuts.isEmpty() && !m_explicitlyReset ? + m_defaultShortcuts : m_customShortcuts; + } + + + private: + QList m_defaultShortcuts; + QList m_customShortcuts; + bool m_explicitlyReset = false; }; // Convenience macros to extract text of a child node. QString getChildContent(QDomElement xml, QString node) { return xml.firstChildElement(node).text(); - }; - - ActionInfoItem emptyActionInfo; // Used as default return value + } // Use Krita debug logging categories instead of KDE's default qDebug() for // harmless empty strings and translations QString quietlyTranslate(const QString &s) { if (s.isEmpty()) { return s; } QString translatedString = i18nc("action", s.toUtf8()); if (translatedString == s) { translatedString = i18n(s.toUtf8()); } if (translatedString.isEmpty()) { dbgAction << "No translation found for" << s; return s; } return translatedString; - }; - - - QList preferredShortcuts(ActionInfoItem action) { - if (action.customShortcuts.isEmpty()) { - return action.defaultShortcuts; - } else { - return action.customShortcuts; - } - }; - -}; + } +} class Q_DECL_HIDDEN KisActionRegistry::Private { public: - Private(KisActionRegistry *_q) : q(_q) {}; + Private(KisActionRegistry *_q) : q(_q) {} // This is the main place containing ActionInfoItems. QMap actionInfoList; void loadActionFiles(); void loadCustomShortcuts(QString filename = QStringLiteral("kritashortcutsrc")); ActionInfoItem &actionInfo(const QString &name) { if (!actionInfoList.contains(name)) { dbgAction << "Tried to look up info for unknown action" << name; } return actionInfoList[name]; - }; + } KisActionRegistry *q; - QMap actionCollections; + QSet sanityPropertizedShortcuts; }; -Q_GLOBAL_STATIC(KisActionRegistry, s_instance); +Q_GLOBAL_STATIC(KisActionRegistry, s_instance) KisActionRegistry *KisActionRegistry::instance() { return s_instance; -}; +} KisActionRegistry::KisActionRegistry() : d(new KisActionRegistry::Private(this)) { d->loadActionFiles(); KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); QString schemeName = cg.readEntry("Current Scheme", "Default"); loadShortcutScheme(schemeName); loadCustomShortcuts(); } -void KisActionRegistry::addAction(const QString &name, QAction *a) +KisActionRegistry::ActionCategory KisActionRegistry::fetchActionCategory(const QString &name) const { - auto info = d->actionInfo(name); - - KActionCollection *collection = d->actionCollections.value(info.collectionName); - if (!collection) { - dbgAction << "No collection found for action" << name; - return; - } - if (collection->action(name)) { - dbgAction << "duplicate action" << name << "in collection" << collection->componentName(); - } - else { - } - collection->addCategorizedAction(name, a, info.categoryName); -}; + if (!d->actionInfoList.contains(name)) return ActionCategory(); + const ActionInfoItem info = d->actionInfoList.value(name); + return ActionCategory(info.collectionName, info.categoryName); +} void KisActionRegistry::notifySettingsUpdated() { d->loadCustomShortcuts(); -}; +} -void KisActionRegistry::loadCustomShortcuts(const QString &path) +void KisActionRegistry::loadCustomShortcuts() { - if (path.isEmpty()) { - d->loadCustomShortcuts(); - } else { - d->loadCustomShortcuts(path); - } -}; + d->loadCustomShortcuts(); +} void KisActionRegistry::loadShortcutScheme(const QString &schemeName) { // Load scheme file if (schemeName != QStringLiteral("Default")) { QString schemeFileName = KShortcutSchemesHelper::schemeFileLocations().value(schemeName); if (schemeFileName.isEmpty()) { return; } KConfig schemeConfig(schemeFileName, KConfig::SimpleConfig); applyShortcutScheme(&schemeConfig); } else { // Apply default scheme, updating KisActionRegistry data applyShortcutScheme(); } } QAction * KisActionRegistry::makeQAction(const QString &name, QObject *parent) { - QAction * a = new QAction(parent); if (!d->actionInfoList.contains(name)) { dbgAction << "Warning: requested data for unknown action" << name; return a; } + propertizeAction(name, a); return a; -}; - - -void KisActionRegistry::setupDialog(KisShortcutsDialog *dlg) -{ - for (auto i = d->actionCollections.constBegin(); i != d->actionCollections.constEnd(); i++ ) { - dlg->addCollection(i.value(), i.key()); - } } void KisActionRegistry::settingsPageSaved() { // For now, custom shortcuts are dealt with by writing to file and reloading. loadCustomShortcuts(); // Announce UI should reload current shortcuts. emit shortcutsUpdated(); } void KisActionRegistry::applyShortcutScheme(const KConfigBase *config) { // First, update the things in KisActionRegistry if (config == 0) { // Use default shortcut scheme. Simplest just to reload everything. d->actionInfoList.clear(); d->loadActionFiles(); loadCustomShortcuts(); } else { const auto schemeEntries = config->group(QStringLiteral("Shortcuts")).entryMap(); // Load info item for each shortcut, reset custom shortcuts auto it = schemeEntries.constBegin(); while (it != schemeEntries.end()) { ActionInfoItem &info = d->actionInfo(it.key()); - if (!it.value().isEmpty()) - info.defaultShortcuts = QKeySequence::listFromString(it.value()); + info.setDefaultShortcuts(QKeySequence::listFromString(it.value())); it++; } } } void KisActionRegistry::updateShortcut(const QString &name, QAction *action) { const ActionInfoItem &info = d->actionInfo(name); - action->setShortcuts(preferredShortcuts(info)); - action->setProperty("defaultShortcuts", qVariantFromValue(info.defaultShortcuts)); + action->setShortcuts(info.effectiveShortcuts()); + action->setProperty("defaultShortcuts", qVariantFromValue(info.defaultShortcuts())); + + d->sanityPropertizedShortcuts.insert(name); } +bool KisActionRegistry::sanityCheckPropertized(const QString &name) +{ + return d->sanityPropertizedShortcuts.contains(name); +} -bool KisActionRegistry::propertizeAction(const QString &name, QAction * a) +QList KisActionRegistry::registeredShortcutIds() const { + return d->actionInfoList.keys(); +} - const ActionInfoItem info = d->actionInfo(name); - QDomElement actionXml = info.xmlData; - if (actionXml.text().isEmpty()) { +bool KisActionRegistry::propertizeAction(const QString &name, QAction * a) +{ + if (!d->actionInfoList.contains(name)) { dbgAction << "No XML data found for action" << name; return false; } + const ActionInfoItem info = d->actionInfo(name); - // i18n requires converting format from QString. - auto getChildContent_i18n = [=](QString node){return quietlyTranslate(getChildContent(actionXml, node));}; - - // Note: the fields in the .action documents marked for translation are determined by extractrc. - QString icon = getChildContent(actionXml, "icon"); - QString text = getChildContent_i18n("text"); - QString whatsthis = getChildContent_i18n("whatsThis"); - QString toolTip = getChildContent_i18n("toolTip"); - QString statusTip = getChildContent_i18n("statusTip"); - QString iconText = getChildContent_i18n("iconText"); - bool isCheckable = getChildContent(actionXml, "isCheckable") == QString("true"); - - - - - a->setObjectName(name); // This is helpful, should be added more places in Krita - a->setIcon(KisIconUtils::loadIcon(icon.toLatin1())); - a->setText(text); - a->setObjectName(name); - a->setWhatsThis(whatsthis); - a->setToolTip(toolTip); - a->setStatusTip(statusTip); - a->setIconText(iconText); - a->setCheckable(isCheckable); + QDomElement actionXml = info.xmlData; + if (!actionXml.text().isEmpty()) { + // i18n requires converting format from QString. + auto getChildContent_i18n = [=](QString node){return quietlyTranslate(getChildContent(actionXml, node));}; + + // Note: the fields in the .action documents marked for translation are determined by extractrc. + QString icon = getChildContent(actionXml, "icon"); + QString text = getChildContent_i18n("text"); + QString whatsthis = getChildContent_i18n("whatsThis"); + QString toolTip = getChildContent_i18n("toolTip"); + QString statusTip = getChildContent_i18n("statusTip"); + QString iconText = getChildContent_i18n("iconText"); + bool isCheckable = getChildContent(actionXml, "isCheckable") == QString("true"); + + a->setObjectName(name); // This is helpful, should be added more places in Krita + a->setIcon(KisIconUtils::loadIcon(icon.toLatin1())); + a->setText(text); + a->setObjectName(name); + a->setWhatsThis(whatsthis); + a->setToolTip(toolTip); + a->setStatusTip(statusTip); + a->setIconText(iconText); + a->setCheckable(isCheckable); + } updateShortcut(name, a); - - - - // TODO: check for colliding shortcuts in .action files either here or in loading code -#if 0 - QMap existingShortcuts; - Q_FOREACH (QAction* action, actionCollection->actions()) { - if(action->shortcut() == QKeySequence(0)) { - continue; - } - if (existingShortcuts.contains(action->shortcut())) { - dbgAction << QString("Actions %1 and %2 have the same shortcut: %3") \ - .arg(action->text()) \ - .arg(existingShortcuts[action->shortcut()]->text()) \ - .arg(action->shortcut()); - } - else { - existingShortcuts[action->shortcut()] = action; - } - } -#endif - return true; } QString KisActionRegistry::getActionProperty(const QString &name, const QString &property) { ActionInfoItem info = d->actionInfo(name); QDomElement actionXml = info.xmlData; if (actionXml.text().isEmpty()) { dbgAction << "No XML data found for action" << name; return QString(); } return getChildContent(actionXml, property); } void KisActionRegistry::Private::loadActionFiles() { QStringList actionDefinitions = KoResourcePaths::findAllResources("kis_actions", "*.action", KoResourcePaths::Recursive); // Extract actions all XML .action files. Q_FOREACH (const QString &actionDefinition, actionDefinitions) { QDomDocument doc; QFile f(actionDefinition); f.open(QFile::ReadOnly); doc.setContent(f.readAll()); QDomElement base = doc.documentElement(); // "ActionCollection" outer group QString collectionName = base.attribute("name"); QString version = base.attribute("version"); if (version != "2") { errAction << ".action XML file" << actionDefinition << "has incorrect version; skipping."; continue; } - - KActionCollection *actionCollection; - if (!actionCollections.contains(collectionName)) { - actionCollection = new KActionCollection(q, collectionName); - actionCollections.insert(collectionName, actionCollection); - dbgAction << "Adding a new action collection " << collectionName; - } else { - actionCollection = actionCollections.value(collectionName); - } - // Loop over nodes. Each of these corresponds to a // KActionCategory, producing a group of actions in the shortcut dialog. QDomElement actions = base.firstChild().toElement(); while (!actions.isNull()) { // field QDomElement categoryTextNode = actions.firstChild().toElement(); QString categoryName = quietlyTranslate(categoryTextNode.text()); - // KActionCategory *category = actionCollection->getCategory(categoryName); - // dbgAction << "Using category" << categoryName; // tags QDomElement actionXml = categoryTextNode.nextSiblingElement(); // Loop over individual actions while (!actionXml.isNull()) { if (actionXml.tagName() == "Action") { // Read name from format QString name = actionXml.attribute("name"); // Bad things if (name.isEmpty()) { errAction << "Unnamed action in definitions file " << actionDefinition; } else if (actionInfoList.contains(name)) { // errAction << "NOT COOL: Duplicated action name from xml data: " << name; } else { ActionInfoItem info; info.xmlData = actionXml; // Use empty list to signify no shortcut QString shortcutText = getChildContent(actionXml, "shortcut"); - if (!shortcutText.isEmpty()) - info.defaultShortcuts << QKeySequence(shortcutText); + if (!shortcutText.isEmpty()) { + info.setDefaultShortcuts(QKeySequence::listFromString(shortcutText)); + } info.categoryName = categoryName; info.collectionName = collectionName; // dbgAction << "default shortcut for" << name << " - " << info.defaultShortcut; actionInfoList.insert(name,info); } } actionXml = actionXml.nextSiblingElement(); } actions = actions.nextSiblingElement(); } } -}; +} void KisActionRegistry::Private::loadCustomShortcuts(QString filename) { const KConfigGroup localShortcuts(KSharedConfig::openConfig(filename), QStringLiteral("Shortcuts")); if (!localShortcuts.exists()) { return; } // Distinguish between two "null" states for custom shortcuts. for (auto i = actionInfoList.begin(); i != actionInfoList.end(); ++i) { if (localShortcuts.hasKey(i.key())) { QString entry = localShortcuts.readEntry(i.key(), QString()); if (entry == QStringLiteral("none")) { - // A shortcut list with a single entry "" means the user has disabled the shortcut. - // This occurs after stealing the shortcut without assigning a new one. - i.value().customShortcuts = QList(); + i.value().setCustomShortcuts(QList(), true); } else { - i.value().customShortcuts = QKeySequence::listFromString(entry); + i.value().setCustomShortcuts(QKeySequence::listFromString(entry), false); } } else { - // An empty shortcut list means no custom shortcut has been set. - i.value().customShortcuts = QList(); + i.value().setCustomShortcuts(QList(), false); } } -}; +} + +KisActionRegistry::ActionCategory::ActionCategory() +{ +} + +KisActionRegistry::ActionCategory::ActionCategory(const QString &_componentName, const QString &_categoryName) + : componentName(_componentName), + categoryName(_categoryName), + m_isValid(true) +{ +} + +bool KisActionRegistry::ActionCategory::isValid() const +{ + return m_isValid && !categoryName.isEmpty() && !componentName.isEmpty(); +} diff --git a/libs/widgetutils/kis_action_registry.h b/libs/widgetutils/kis_action_registry.h index f5303c9877..9d3326b7e5 100644 --- a/libs/widgetutils/kis_action_registry.h +++ b/libs/widgetutils/kis_action_registry.h @@ -1,137 +1,154 @@ /* * Copyright (c) 2015 Michael Abrahams * * 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 3 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_ACTION_REGISTRY_H +#define KIS_ACTION_REGISTRY_H #include #include #include #include +#include #include "kritawidgetutils_export.h" class KActionCollection; class QDomElement; class KConfigBase; class KisShortcutsDialog; /** * KisActionRegistry is intended to manage the global action configuration data * for Krita. The data come from four sources: * - .action files, containing static action configuration data in XML format, * - .rc configuration files, originally from XMLGUI and now in WidgetUtils, * - kritashortcutsrc, containing temporary shortcut configuration, and * - .shortcuts scheme files providing sets of default shortcuts, also from XMLGUI * * This class can be used as a factory by calling makeQAction. It can be used to * add standard properties such as default shortcuts and default tooltip to an * existing action with propertizeAction. If you have a custom action class * which needs to add other properties, you can use propertizeAction to add any * sort of data you wish to the .action configuration file. * * This class is also in charge of displaying the shortcut configuration dialog. * The interplay between this class, KActionCollection, KisShortcutsEditor and * so on can be complex, and is sometimes synchronized by file I/O by reading * and writing the configuration files mentioned above. * * It is a global static. Grab an ::instance(). */ class KRITAWIDGETUTILS_EXPORT KisActionRegistry : public QObject { Q_OBJECT public: static KisActionRegistry *instance(); /** * @return value @p property for an action @p name. * * Allow flexible info structure for KisActions, etc. */ QString getActionProperty(const QString &name, const QString &property); /** * Saves action in a category. Note that this grabs ownership of the action. */ void addAction(const QString &name, QAction *a); /** * Produces a new QAction based on the .action data files. * * N.B. this action will not be saved in the registry. */ QAction * makeQAction(const QString &name, QObject *parent); /** * Fills the standard QAction properties of an action. * * @return true if the action was loaded successfully. */ bool propertizeAction(const QString &name, QAction *a); - /** - * Setup the shortcut configuration widget. - */ - void setupDialog(KisShortcutsDialog *dlg); - - /** * Called when "OK" button is pressed in settings dialog. */ void settingsPageSaved(); /** * Reload custom shortcuts from kritashortcutsrc */ - void loadCustomShortcuts(const QString &path = QString()); + void loadCustomShortcuts(); /** * Call after settings are changed. */ void notifySettingsUpdated(); // If config == 0, reload defaults void applyShortcutScheme(const KConfigBase *config = 0); + struct ActionCategory { + ActionCategory(); + ActionCategory(const QString &_componentName, const QString &_categoryName); + QString componentName; + QString categoryName; + + bool isValid() const; + + private: + bool m_isValid = false; + }; + + ActionCategory fetchActionCategory(const QString &name) const; + /** * Constructor. Please don't touch! */ KisActionRegistry(); /** * @brief loadShortcutScheme * @param schemeName */ void loadShortcutScheme(const QString &schemeName); // Undocumented void updateShortcut(const QString &name, QAction *ac); + bool sanityCheckPropertized(const QString &name); + + QList registeredShortcutIds() const; + Q_SIGNALS: void shortcutsUpdated(); private: class Private; Private * const d; }; + +#endif /* KIS_ACTION_REGISTRY_H */ diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp index 5ac833889f..5bf8fb979e 100644 --- a/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp +++ b/libs/widgetutils/xmlgui/KisShortcutsDialog.cpp @@ -1,147 +1,152 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Mark Donohoe Copyright (C) 1997 Nicolas Hadacek Copyright (C) 1998 Mark Donohoe Copyright (C) 1998 Matthias Ettrich Copyright (C) 1999 Espen Sand Copyright (C) 2001 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Roberto Raggi Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen Copyright (C) 2008 Alexander Dymo Copyright (C) 2009 Chani Armitage 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 "KisShortcutsDialog.h" #include "KisShortcutsDialog_p.h" #include "kshortcutschemeshelper_p.h" #include "kshortcutschemeseditor.h" #include #include #include #include #include #include #include #include #include "kxmlguiclient.h" #include "kxmlguifactory.h" #include "kactioncollection.h" KisShortcutsDialog::KisShortcutsDialog(KisShortcutsEditor::ActionTypes types, KisShortcutsEditor::LetterShortcuts allowLetterShortcuts, QWidget *parent) : QWidget(parent) , d(new KisShortcutsDialogPrivate(this)) { d->m_shortcutsEditor = new KisShortcutsEditor(this, types, allowLetterShortcuts); /* Construct & Connect UI */ QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(d->m_shortcutsEditor); QHBoxLayout *bottomLayout = new QHBoxLayout; d->m_schemeEditor = new KShortcutSchemesEditor(this); connect(d->m_schemeEditor, SIGNAL(shortcutsSchemeChanged(QString)), this, SLOT(changeShortcutScheme(QString))); bottomLayout->addLayout(d->m_schemeEditor); QPushButton *printButton = new QPushButton; KGuiItem::assign(printButton, KStandardGuiItem::print()); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->addButton(printButton, QDialogButtonBox::ActionRole); bottomLayout->addWidget(buttonBox); mainLayout->addLayout(bottomLayout); connect(printButton, SIGNAL(clicked()), d->m_shortcutsEditor, SLOT(printShortcuts())); KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings"); resize(group.readEntry("Dialog Size", sizeHint())); } KisShortcutsDialog::~KisShortcutsDialog() { KConfigGroup group(KSharedConfig::openConfig(), "KisShortcutsDialog Settings"); group.writeEntry("Dialog Size", size()); delete d; } void KisShortcutsDialog::addCollection(KActionCollection *collection, const QString &title) { d->m_shortcutsEditor->addCollection(collection, title); d->m_collections.insert(title, collection); } void KisShortcutsDialog::save() { d->save(); } QList KisShortcutsDialog::actionCollections() const { return d->m_collections.values(); } QSize KisShortcutsDialog::sizeHint() const { return QSize(600, 480); } void KisShortcutsDialog::allDefault() { d->m_shortcutsEditor->allDefault(); } +void KisShortcutsDialog::undo() +{ + d->undo(); +} + void KisShortcutsDialog::importConfiguration(const QString &path) { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->importConfiguration(config.data(), true); } void KisShortcutsDialog::exportConfiguration(const QString &path) const { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->exportConfiguration(config.data()); } void KisShortcutsDialog::saveCustomShortcuts(const QString &path) const { auto cg = KSharedConfig::openConfig(path)->group(QStringLiteral("Shortcuts")); d->m_shortcutsEditor->saveShortcuts(&cg); d->m_shortcutsEditor->commit(); } void KisShortcutsDialog::loadCustomShortcuts(const QString &path) { auto config = KSharedConfig::openConfig(path); d->m_shortcutsEditor->importConfiguration(config.data(), false); } #include "moc_KisShortcutsDialog.cpp" diff --git a/libs/widgetutils/xmlgui/KisShortcutsDialog.h b/libs/widgetutils/xmlgui/KisShortcutsDialog.h index 4e177884e4..756c742475 100644 --- a/libs/widgetutils/xmlgui/KisShortcutsDialog.h +++ b/libs/widgetutils/xmlgui/KisShortcutsDialog.h @@ -1,180 +1,178 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Nicolas Hadacek Copyright (C) 2001 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Roberto Raggi Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen Copyright (C) 2015 Michael Abrahams 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 KISSHORTCUTSDIALOG_H #define KISSHORTCUTSDIALOG_H #include #include // Altering this class and some classes it uses was one major impetus behind // forking XmlGui. The first major workaround was to allow // KisPart::configureShortcuts() to pull up the dialog, and to remote the scheme // editor support, since it's incompatible with Krita. // // The files were forked from KF5 XmlGui version 5.12.0 // dialogs/KisShortcutsEditorItem.cpp <- kshortcutseditoritem.cpp // dialogs/KisShortcutEditWidget.cpp <- kshortcuteditwidget.cpp // dialogs/KisShortcutsEditorDelegate.cpp <- kshortcutseditordelegate.cpp // dialogs/KisShortcutsDialog.cpp <- kshortcutsdialog.cpp, , kshortcutsdialog_p.cpp // dialogs/KisShortcutsDialog.h <- kshortcutsdialog.h // dialogs/KisShortcutsDialog_p.h <- kshortcutsdialog_p.h, kshortcutseditor_p.h // forms/KisShortcutsDialog.ui <- kshortcutsdialog.ui // // // Changes that have been done to the files: // * Adapt of includes // * Removing unwanted parts related to schemes // * Renamed KShortcutsDialog/Editor to KisShortcutsDialog/Editor // * Add export macro // * Split apart kshortcutseditor_p // * Copied KShortcutsEditorPrivate::itemFromIndex() implmentation from // KF5 XmlGui's kshortcutseditor.cpp to begin of KisShortcutsEditorItem.cpp /** * @short Dialog for configuration of KActionCollection and KGlobalAccel. * * The KisShortcutsDialog class is used for configuring dictionaries of * key/action associations for KActionCollection and KGlobalAccel. It uses the * KShortcutsEditor widget and offers buttons to set all keys to defaults and * invoke on-line help. * * Several static methods are supplied which provide the most convenient * interface to the dialog. The most common and most encouraged use is with * KActionCollection. * * \code * KisShortcutsDialog::configure( actionCollection() ); * \endcode * * @since 4.3 By default this dialog is modal. If you don't want that, * setModal(false) and then the non-static configure() will show the dialog. If * you want to do anything extra when the dialog is done, connect to okClicked() * and/or cancelClicked(). However, if your extra stuff depends on the changed * settings already being saved, connect to saved() instead to be safe; if you * connect to okClicked() your function might be called before the save happens. * * example: * \code * KisShortcutsDialog dlg; * dlg.addCollection(myActions); * dlg.setModal(false); * connect(&dlg, SIGNAL(saved()), this, SLOT(doExtraStuff())); * dlg.configure(); * \endcode * * \image html kshortcutsdialog.png "KDE Shortcuts Dialog" * * @author Nicolas Hadacek * @author Hamish Rodda (KDE 4 porting) * @author Michael Jansen */ const auto defaultActionTypes = KisShortcutsEditor::WidgetAction \ | KisShortcutsEditor::WindowAction \ | KisShortcutsEditor::ApplicationAction; class KRITAWIDGETUTILS_EXPORT KisShortcutsDialog : public QWidget { Q_OBJECT public: /** * Constructs a KisShortcutsDialog. Mostly UI boilerplate. * * @param allowLetterShortcuts set to KisShortcutsEditor::LetterShortcutsDisallowed if unmodified alphanumeric * keys ('A', '1', etc.) are not permissible shortcuts. * * @param parent the parent widget to attach to * * There is some legacy support for global (i.e. desktop-wide) shortucts * that should probably be removed. */ explicit KisShortcutsDialog(KisShortcutsEditor::ActionTypes types = defaultActionTypes, KisShortcutsEditor::LetterShortcuts allowLetterShortcuts \ = KisShortcutsEditor::LetterShortcutsAllowed, QWidget *parent = 0); virtual ~KisShortcutsDialog(); /** * Add all actions of the collection to the ones displayed and configured * by the dialog. This is where the business happens. * * @param title the title associated with the collection. */ void addCollection(KActionCollection *, const QString &title = QString()); /** * @return the list of action collections that are available for configuration in the dialog. */ QList actionCollections() const; /** @see QWidget::sizeHint() */ QSize sizeHint() const override; /** * Called when the "OK" button in the main configuration page is pressed. */ void save(); + void allDefault(); + void undo(); /** * Import shortcut scheme file from @p path */ void importConfiguration(const QString &path); /** * Exports shortcut scheme file to @p path */ void exportConfiguration(const QString &path) const; /** * Import custom shortcuts from @p path */ void loadCustomShortcuts(const QString &path); /** * Exports custom shortcuts to @p path */ void saveCustomShortcuts(const QString &path) const; -public Q_SLOTS: - - void allDefault(); - private: Q_PRIVATE_SLOT(d, void changeShortcutScheme(const QString &)) Q_PRIVATE_SLOT(d, void undo()) class KisShortcutsDialogPrivate; class KisShortcutsDialogPrivate *const d; Q_DISABLE_COPY(KisShortcutsDialog) }; #endif // KISSHORTCUTSDIALOG_H diff --git a/libs/widgetutils/xmlgui/kactionconflictdetector.cpp b/libs/widgetutils/xmlgui/kactionconflictdetector.cpp index 594440c1be..4d0a8046cb 100644 --- a/libs/widgetutils/xmlgui/kactionconflictdetector.cpp +++ b/libs/widgetutils/xmlgui/kactionconflictdetector.cpp @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2005-2006 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 #include #include #include #include class KActionConflictDetector : public QObject { public: explicit KActionConflictDetector(QObject *parent = 0) : QObject(parent) { } bool eventFilter(QObject *watched, QEvent *event) override { if (qobject_cast(watched) && (event->type() == QEvent::Shortcut)) { QShortcutEvent *se = static_cast(event); if (se->isAmbiguous()) { KMessageBox::information( 0, // No widget to be seen around here - i18n("The key sequence '%1' is ambiguous. Use 'Configure Shortcuts'\n" - "from the 'Settings' menu to solve the ambiguity.\n" + i18n("The key sequence '%1' is ambiguous. Use the 'Keyboard Shortcuts'\n" + "tab in 'Settings->Configure Krita...' dialog to solve the ambiguity.\n" "No action will be triggered.", se->key().toString(QKeySequence::NativeText)), i18n("Ambiguous shortcut detected")); return true; } } return QObject::eventFilter(watched, event); } }; void _k_installConflictDetector() { QCoreApplication *app = QCoreApplication::instance(); app->installEventFilter(new KActionConflictDetector(app)); } Q_COREAPP_STARTUP_FUNCTION(_k_installConflictDetector) diff --git a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp index 1b428f999c..fecb32d29c 100644 --- a/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp +++ b/plugins/dockers/advancedcolorselector/kis_color_selector_ng_docker_widget.cpp @@ -1,224 +1,219 @@ /* * Copyright (c) 2010 Adam Celarek * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_selector_ng_docker_widget.h" #include "ui_wdg_color_selector_settings.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_canvas_resource_provider.h" #include "kis_color_space_selector.h" #include "kis_preference_set_registry.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_color_history.h" #include "kis_common_colors.h" #include "kis_color_selector_settings.h" #include "kis_color_selector_container.h" #include "kis_action_registry.h" KisColorSelectorNgDockerWidget::KisColorSelectorNgDockerWidget(QWidget *parent) : QWidget(parent), m_colorHistoryAction(0), m_commonColorsAction(0), m_verticalColorPatchesLayout(0), m_horizontalColorPatchesLayout(0), m_canvas(0) { setAutoFillBackground(true); m_colorSelectorContainer = new KisColorSelectorContainer(this); m_colorHistoryWidget = new KisColorHistory(this); m_commonColorsWidget = new KisCommonColors(this); //default settings //remember to also change the default in the ui file //shade selector //layout m_verticalColorPatchesLayout = new QHBoxLayout(); m_verticalColorPatchesLayout->setSpacing(0); m_verticalColorPatchesLayout->setMargin(0); m_verticalColorPatchesLayout->addWidget(m_colorSelectorContainer); m_horizontalColorPatchesLayout = new QVBoxLayout(this); m_horizontalColorPatchesLayout->setSpacing(0); m_horizontalColorPatchesLayout->setMargin(0); m_horizontalColorPatchesLayout->addLayout(m_verticalColorPatchesLayout); updateLayout(); connect(m_colorSelectorContainer, SIGNAL(openSettings()), this, SLOT(openSettings())); //emit settingsChanged() if the settings are changed in krita preferences KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); KisColorSelectorSettingsFactory* factory = dynamic_cast(preferenceSetRegistry->get("KisColorSelectorSettingsFactory")); Q_ASSERT(factory); connect(&(factory->repeater), SIGNAL(settingsUpdated()), this, SIGNAL(settingsChanged()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), this, SLOT(updateLayout()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_commonColorsWidget, SLOT(updateSettings()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_colorHistoryWidget, SLOT(updateSettings()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), m_colorSelectorContainer, SIGNAL(settingsChanged()), Qt::UniqueConnection); connect(this, SIGNAL(settingsChanged()), this, SLOT(update()), Qt::UniqueConnection); emit settingsChanged(); - m_colorHistoryAction = new QAction("Show color history", this); - m_colorHistoryAction->setShortcut(QKeySequence(tr("H"))); - // m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_color_history", this); + m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_color_history", this); connect(m_colorHistoryAction, SIGNAL(triggered()), m_colorHistoryWidget, SLOT(showPopup()), Qt::UniqueConnection); - - m_commonColorsAction = new QAction("Show common colors", this); - m_commonColorsAction->setShortcut(QKeySequence(tr("U"))); - // m_colorHistoryAction = KisActionRegistry::instance()->makeQAction("show_common_colors", this); + m_commonColorsAction = KisActionRegistry::instance()->makeQAction("show_common_colors", this); connect(m_commonColorsAction, SIGNAL(triggered()), m_commonColorsWidget, SLOT(showPopup()), Qt::UniqueConnection); } void KisColorSelectorNgDockerWidget::unsetCanvas() { m_canvas = 0; m_commonColorsWidget->unsetCanvas(); m_colorHistoryWidget->unsetCanvas(); m_colorSelectorContainer->unsetCanvas(); } void KisColorSelectorNgDockerWidget::setCanvas(KisCanvas2 *canvas) { if (m_canvas) { m_canvas->disconnect(this); KActionCollection *ac = m_canvas->viewManager()->actionCollection(); ac->takeAction(ac->action("show_color_history")); ac->takeAction(ac->action("show_common_colors")); } m_canvas = canvas; m_commonColorsWidget->setCanvas(canvas); m_colorHistoryWidget->setCanvas(canvas); m_colorSelectorContainer->setCanvas(canvas); if (m_canvas && m_canvas->viewManager()) { if (m_canvas->viewManager()->nodeManager()) { connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)), SLOT(reactOnLayerChange()), Qt::UniqueConnection); } KActionCollection* actionCollection = canvas->viewManager()->actionCollection(); actionCollection->addAction("show_color_history", m_colorHistoryAction); actionCollection->addAction("show_common_colors", m_commonColorsAction); } reactOnLayerChange(); } void KisColorSelectorNgDockerWidget::openSettings() { if (!m_canvas) return; KisColorSelectorSettingsDialog settings; if(settings.exec()==QDialog::Accepted) { emit settingsChanged(); } } void KisColorSelectorNgDockerWidget::updateLayout() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); //color patches bool m_lastColorsShow = cfg.readEntry("lastUsedColorsShow", true); KisColorPatches::Direction m_lastColorsDirection; if(cfg.readEntry("lastUsedColorsAlignment", false)) m_lastColorsDirection=KisColorPatches::Vertical; else m_lastColorsDirection=KisColorPatches::Horizontal; bool m_commonColorsShow = cfg.readEntry("commonColorsShow", true); KisColorPatches::Direction m_commonColorsDirection; if(cfg.readEntry("commonColorsAlignment", false)) m_commonColorsDirection=KisColorPatches::Vertical; else m_commonColorsDirection=KisColorPatches::Horizontal; m_verticalColorPatchesLayout->removeWidget(m_colorHistoryWidget); m_verticalColorPatchesLayout->removeWidget(m_commonColorsWidget); m_horizontalColorPatchesLayout->removeWidget(m_colorHistoryWidget); m_horizontalColorPatchesLayout->removeWidget(m_commonColorsWidget); if(m_lastColorsShow==false) m_colorHistoryWidget->hide(); else m_colorHistoryWidget->show(); if(m_commonColorsShow==false) { m_commonColorsWidget->hide(); } else { m_commonColorsWidget->show(); } if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Vertical) { m_verticalColorPatchesLayout->addWidget(m_colorHistoryWidget); } if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Vertical) { m_verticalColorPatchesLayout->addWidget(m_commonColorsWidget); } if(m_lastColorsShow && m_lastColorsDirection==KisColorPatches::Horizontal) { m_horizontalColorPatchesLayout->addWidget(m_colorHistoryWidget); } if(m_commonColorsShow && m_commonColorsDirection==KisColorPatches::Horizontal) { m_horizontalColorPatchesLayout->addWidget(m_commonColorsWidget); } updateGeometry(); } void KisColorSelectorNgDockerWidget::reactOnLayerChange() { /** * Trigger the update for the case if some legacy code needs it. * Now the node's color space is managed by the * KisDisplayColorConverter and KisColorSelectorBase objects, so * technically this call is not needed anymore. Please remove it * when you are totally sure this will not break something. */ emit settingsChanged(); } diff --git a/plugins/dockers/animation/animation_docker.cpp b/plugins/dockers/animation/animation_docker.cpp index e5e9c88db6..f382d66d4e 100644 --- a/plugins/dockers/animation/animation_docker.cpp +++ b/plugins/dockers/animation/animation_docker.cpp @@ -1,663 +1,664 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "animation_docker.h" #include "kis_global.h" #include "kis_canvas2.h" #include "kis_image.h" #include #include #include #include "KisViewManager.h" #include "kis_action_manager.h" #include "kis_image_animation_interface.h" #include "kis_animation_player.h" #include "kis_time_range.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include "kis_keyframe_channel.h" #include "kis_animation_utils.h" #include "krita_utils.h" #include "kis_image_config.h" #include "kis_config.h" #include "kis_signals_blocker.h" #include "kis_node_manager.h" #include "kis_transform_mask_params_factory_registry.h" #include "ui_wdg_animation.h" void setupActionButton(const QString &text, KisAction::ActivationFlags flags, bool defaultValue, QToolButton *button, KisAction **action) { *action = new KisAction(text, button); (*action)->setActivationFlags(flags); (*action)->setCheckable(true); (*action)->setChecked(defaultValue); button->setDefaultAction(*action); } AnimationDocker::AnimationDocker() : QDockWidget(i18n("Animation")) , m_canvas(0) , m_animationWidget(new Ui_WdgAnimation) , m_mainWindow(0) { QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_animationWidget->setupUi(mainWidget); + // TODO: create all the actions using action manager! m_previousFrameAction = new KisAction(i18n("Previous Frame"), m_animationWidget->btnPreviousFrame); m_previousFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnPreviousFrame->setDefaultAction(m_previousFrameAction); m_nextFrameAction = new KisAction(i18n("Next Frame"), m_animationWidget->btnNextFrame); m_nextFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnNextFrame->setDefaultAction(m_nextFrameAction); m_previousKeyFrameAction = new KisAction(i18n("Previous Key Frame"), m_animationWidget->btnPreviousKeyFrame); m_previousKeyFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnPreviousKeyFrame->setDefaultAction(m_previousKeyFrameAction); m_nextKeyFrameAction = new KisAction(i18n("Next Key Frame"), m_animationWidget->btnNextKeyFrame); m_nextKeyFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnNextKeyFrame->setDefaultAction(m_nextKeyFrameAction); m_firstFrameAction = new KisAction(i18n("First Frame"), m_animationWidget->btnFirstFrame); m_firstFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnFirstFrame->setDefaultAction(m_firstFrameAction); m_lastFrameAction = new KisAction(i18n("Last Frame"), m_animationWidget->btnLastFrame); m_lastFrameAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnLastFrame->setDefaultAction(m_lastFrameAction); m_playPauseAction = new KisAction(i18n("Play / Pause"), m_animationWidget->btnPlay); m_playPauseAction->setActivationFlags(KisAction::ACTIVE_IMAGE); m_animationWidget->btnPlay->setDefaultAction(m_playPauseAction); m_addBlankFrameAction = new KisAction(KisAnimationUtils::addFrameActionName, m_animationWidget->btnAddKeyframe); m_addBlankFrameAction->setActivationFlags(KisAction::ACTIVE_NODE); m_animationWidget->btnAddKeyframe->setDefaultAction(m_addBlankFrameAction); m_addDuplicateFrameAction = new KisAction(KisAnimationUtils::duplicateFrameActionName, m_animationWidget->btnAddDuplicateFrame); m_addDuplicateFrameAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_animationWidget->btnAddDuplicateFrame->setDefaultAction(m_addDuplicateFrameAction); m_deleteKeyframeAction = new KisAction(KisAnimationUtils::removeFrameActionName, m_animationWidget->btnDeleteKeyframe); m_deleteKeyframeAction->setActivationFlags(KisAction::ACTIVE_NODE); m_animationWidget->btnDeleteKeyframe->setDefaultAction(m_deleteKeyframeAction); m_newKeyframeMenu = new QMenu(this); m_animationWidget->btnAddKeyframe->setMenu(m_newKeyframeMenu); m_animationWidget->btnAddKeyframe->setPopupMode(QToolButton::MenuButtonPopup); m_deleteKeyframeMenu = new QMenu(this); m_animationWidget->btnDeleteKeyframe->setMenu(m_deleteKeyframeMenu); m_animationWidget->btnDeleteKeyframe->setPopupMode(QToolButton::MenuButtonPopup); m_addOpacityKeyframeAction = new KisAction(KisAnimationUtils::addOpacityKeyframeActionName); m_deleteOpacityKeyframeAction = new KisAction(KisAnimationUtils::removeOpacityKeyframeActionName); m_addTransformKeyframeAction = new KisAction(KisAnimationUtils::addTransformKeyframeActionName); m_deleteTransformKeyframeAction = new KisAction(KisAnimationUtils::removeTransformKeyframeActionName); { KisImageConfig cfg; setupActionButton(KisAnimationUtils::lazyFrameCreationActionName, KisAction::ACTIVE_IMAGE, cfg.lazyFrameCreationEnabled(), m_animationWidget->btnLazyFrame, &m_lazyFrameAction); } { KisConfig cfg; setupActionButton(KisAnimationUtils::dropFramesActionName, KisAction::ACTIVE_IMAGE, cfg.animationDropFrames(), m_animationWidget->btnDropFrames, &m_dropFramesAction); } QFont font; font.setPointSize(1.7 * font.pointSize()); font.setBold(true); m_animationWidget->intCurrentTime->setFont(font); connect(m_previousFrameAction, SIGNAL(triggered()), this, SLOT(slotPreviousFrame())); connect(m_nextFrameAction, SIGNAL(triggered()), this, SLOT(slotNextFrame())); connect(m_previousKeyFrameAction, SIGNAL(triggered()), this, SLOT(slotPreviousKeyFrame())); connect(m_nextKeyFrameAction, SIGNAL(triggered()), this, SLOT(slotNextKeyFrame())); connect(m_firstFrameAction, SIGNAL(triggered()), this, SLOT(slotFirstFrame())); connect(m_lastFrameAction, SIGNAL(triggered()), this, SLOT(slotLastFrame())); connect(m_playPauseAction, SIGNAL(triggered()), this, SLOT(slotPlayPause())); connect(m_addBlankFrameAction, SIGNAL(triggered()), this, SLOT(slotAddBlankFrame())); connect(m_addDuplicateFrameAction, SIGNAL(triggered()), this, SLOT(slotAddDuplicateFrame())); connect(m_deleteKeyframeAction, SIGNAL(triggered()), this, SLOT(slotDeleteKeyframe())); connect(m_lazyFrameAction, SIGNAL(toggled(bool)), this, SLOT(slotLazyFrameChanged(bool))); connect(m_dropFramesAction, SIGNAL(toggled(bool)), this, SLOT(slotDropFramesChanged(bool))); connect(m_addOpacityKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotAddOpacityKeyframe())); connect(m_deleteOpacityKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotDeleteOpacityKeyframe())); connect(m_addTransformKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotAddTransformKeyframe())); connect(m_deleteTransformKeyframeAction, SIGNAL(triggered(bool)), this, SLOT(slotDeleteTransformKeyframe())); m_animationWidget->btnOnionSkinOptions->setToolTip(i18n("Onion Skins")); connect(m_animationWidget->btnOnionSkinOptions, SIGNAL(clicked()), this, SLOT(slotOnionSkinOptions())); connect(m_animationWidget->spinFromFrame, SIGNAL(valueChanged(int)), this, SLOT(slotUIRangeChanged())); connect(m_animationWidget->spinToFrame, SIGNAL(valueChanged(int)), this, SLOT(slotUIRangeChanged())); connect(m_animationWidget->intFramerate, SIGNAL(valueChanged(int)), this, SLOT(slotUIFramerateChanged())); connect(m_animationWidget->intCurrentTime, SIGNAL(valueChanged(int)), SLOT(slotTimeSpinBoxChanged())); } AnimationDocker::~AnimationDocker() { delete m_animationWidget; } void AnimationDocker::setCanvas(KoCanvasBase * canvas) { if(m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_canvas->image()->disconnect(this); m_canvas->image()->animationInterface()->disconnect(this); m_canvas->animationPlayer()->disconnect(this); m_canvas->viewManager()->nodeManager()->disconnect(this); } m_canvas = dynamic_cast(canvas); if (m_canvas && m_canvas->image()) { KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); { KisSignalsBlocker bloker(m_animationWidget->spinFromFrame, m_animationWidget->spinToFrame, m_animationWidget->intFramerate); m_animationWidget->spinFromFrame->setValue(animation->fullClipRange().start()); m_animationWidget->spinToFrame->setValue(animation->fullClipRange().end()); m_animationWidget->intFramerate->setValue(animation->framerate()); } connect(animation, SIGNAL(sigUiTimeChanged(int)), this, SLOT(slotGlobalTimeChanged())); connect(m_canvas->animationPlayer(), SIGNAL(sigFrameChanged()), this, SLOT(slotGlobalTimeChanged())); connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(slotGlobalTimeChanged())); connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStopped()), this, SLOT(updatePlayPauseIcon())); connect(m_canvas->animationPlayer(), SIGNAL(sigPlaybackStatisticsUpdated()), this, SLOT(updateDropFramesIcon())); connect(m_animationWidget->doublePlaySpeed, SIGNAL(valueChanged(double)), m_canvas->animationPlayer(), SLOT(slotUpdatePlaybackSpeed(double))); connect(m_canvas->viewManager()->nodeManager(), SIGNAL(sigNodeActivated(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); slotGlobalTimeChanged(); slotCurrentNodeChanged(m_canvas->viewManager()->nodeManager()->activeNode()); } } void AnimationDocker::unsetCanvas() { setCanvas(0); } void AnimationDocker::setMainWindow(KisViewManager *view) { KisActionManager *actionManager = view->actionManager(); actionManager->addAction("previous_frame", m_previousFrameAction); actionManager->addAction("next_frame", m_nextFrameAction); actionManager->addAction("previous_keyframe", m_previousKeyFrameAction); actionManager->addAction("next_keyframe", m_nextKeyFrameAction); actionManager->addAction("first_frame", m_firstFrameAction); actionManager->addAction("last_frame", m_lastFrameAction); actionManager->addAction("lazy_frame", m_lazyFrameAction); actionManager->addAction("drop_frames", m_dropFramesAction); actionManager->addAction("toggle_playback", m_playPauseAction); actionManager->addAction("add_blank_frame", m_addBlankFrameAction); actionManager->addAction("add_duplicate_frame", m_addDuplicateFrameAction); actionManager->addAction("delete_keyframe", m_deleteKeyframeAction); slotUpdateIcons(); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); m_mainWindow = view->mainWindow(); } void AnimationDocker::slotAddBlankFrame() { addKeyframe(KisKeyframeChannel::Content.id(), false); } void AnimationDocker::slotAddDuplicateFrame() { addKeyframe(KisKeyframeChannel::Content.id(), true); } void AnimationDocker::slotDeleteKeyframe() { deleteKeyframe(KisKeyframeChannel::Content.id()); } void AnimationDocker::slotAddOpacityKeyframe() { addKeyframe(KisKeyframeChannel::Opacity.id(), false); } void AnimationDocker::slotDeleteOpacityKeyframe() { deleteKeyframe(KisKeyframeChannel::Opacity.id()); } void AnimationDocker::slotAddTransformKeyframe() { if (!m_canvas) return; KisTransformMask *mask = dynamic_cast(m_canvas->viewManager()->activeNode().data()); if (!mask) return; const int time = m_canvas->image()->animationInterface()->currentTime(); KUndo2Command *command = new KUndo2Command(kundo2_i18n("Add transform keyframe")); KisTransformMaskParamsFactoryRegistry::instance()->autoAddKeyframe(mask, time, KisTransformMaskParamsInterfaceSP(), command); command->redo(); m_canvas->currentImage()->postExecutionUndoAdapter()->addCommand(toQShared(command)); } void AnimationDocker::slotDeleteTransformKeyframe() { deleteKeyframe(KisKeyframeChannel::TransformArguments.id()); } void AnimationDocker::slotUIRangeChanged() { if (!m_canvas || !m_canvas->image()) return; int fromTime = m_animationWidget->spinFromFrame->value(); int toTime = m_animationWidget->spinToFrame->value(); m_canvas->image()->animationInterface()->setFullClipRange(KisTimeRange::fromTime(fromTime, toTime)); } void AnimationDocker::slotUIFramerateChanged() { if (!m_canvas || !m_canvas->image()) return; m_canvas->image()->animationInterface()->setFramerate(m_animationWidget->intFramerate->value()); } void AnimationDocker::slotOnionSkinOptions() { if (m_mainWindow) { QDockWidget *docker = m_mainWindow->dockWidget("OnionSkinsDocker"); if (docker) { docker->setVisible(!docker->isVisible()); } } } void AnimationDocker::slotGlobalTimeChanged() { int time = m_canvas->animationPlayer()->isPlaying() ? m_canvas->animationPlayer()->currentTime() : m_canvas->image()->animationInterface()->currentUITime(); m_animationWidget->intCurrentTime->setValue(time); const int frameRate = m_canvas->image()->animationInterface()->framerate(); const int msec = 1000 * time / frameRate; QTime realTime; realTime = realTime.addMSecs(msec); QString realTimeString = realTime.toString("hh:mm:ss.zzz"); m_animationWidget->intCurrentTime->setToolTip(realTimeString); } void AnimationDocker::slotTimeSpinBoxChanged() { if (!m_canvas || !m_canvas->image()) return; int newTime = m_animationWidget->intCurrentTime->value(); KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); if (m_canvas->animationPlayer()->isPlaying() || newTime == animation->currentUITime()) { return; } animation->requestTimeSwitchWithUndo(newTime); } void AnimationDocker::slotPreviousFrame() { if (!m_canvas) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); int time = animation->currentUITime() - 1; if (time >= 0) { animation->requestTimeSwitchWithUndo(time); } } void AnimationDocker::slotNextFrame() { if (!m_canvas) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); int time = animation->currentUITime() + 1; animation->requestTimeSwitchWithUndo(time); } void AnimationDocker::slotPreviousKeyFrame() { if (!m_canvas) return; KisNodeSP node = m_canvas->viewManager()->activeNode(); if (!node) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); int time = animation->currentUITime(); KisKeyframeChannel *content = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!content) return; KisKeyframeSP dstKeyframe; KisKeyframeSP keyframe = content->keyframeAt(time); if (!keyframe) { dstKeyframe = content->activeKeyframeAt(time); } else { dstKeyframe = content->previousKeyframe(keyframe); } if (dstKeyframe) { animation->requestTimeSwitchWithUndo(dstKeyframe->time()); } } void AnimationDocker::slotNextKeyFrame() { if (!m_canvas) return; KisNodeSP node = m_canvas->viewManager()->activeNode(); if (!node) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); int time = animation->currentUITime(); KisKeyframeChannel *content = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!content) return; KisKeyframeSP dstKeyframe; KisKeyframeSP keyframe = content->activeKeyframeAt(time); if (keyframe) { dstKeyframe = content->nextKeyframe(keyframe); } if (dstKeyframe) { animation->requestTimeSwitchWithUndo(dstKeyframe->time()); } } void AnimationDocker::slotFirstFrame() { if (!m_canvas) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); animation->requestTimeSwitchWithUndo(0); } void AnimationDocker::slotLastFrame() { if (!m_canvas) return; KisImageAnimationInterface *animation = m_canvas->image()->animationInterface(); animation->requestTimeSwitchWithUndo(animation->totalLength() - 1); } void AnimationDocker::slotPlayPause() { if (!m_canvas) return; if (m_canvas->animationPlayer()->isPlaying()) { m_canvas->animationPlayer()->stop(); } else { m_canvas->animationPlayer()->play(); } updatePlayPauseIcon(); } void AnimationDocker::updatePlayPauseIcon() { bool isPlaying = m_canvas && m_canvas->animationPlayer() && m_canvas->animationPlayer()->isPlaying(); m_playPauseAction->setIcon(isPlaying ? KisIconUtils::loadIcon("animation_stop") : KisIconUtils::loadIcon("animation_play")); } void AnimationDocker::updateLazyFrameIcon() { KisImageConfig cfg; const bool value = cfg.lazyFrameCreationEnabled(); m_lazyFrameAction->setIcon(value ? KisIconUtils::loadIcon("lazyframeOn") : KisIconUtils::loadIcon("lazyframeOff")); m_lazyFrameAction->setText(QString("%1 (%2)") .arg(KisAnimationUtils::lazyFrameCreationActionName) .arg(KritaUtils::toLocalizedOnOff(value))); } void AnimationDocker::updateDropFramesIcon() { qreal effectiveFps = 0.0; qreal realFps = 0.0; qreal framesDropped = 0.0; bool isPlaying = false; KisAnimationPlayer *player = m_canvas && m_canvas->animationPlayer() ? m_canvas->animationPlayer() : 0; if (player) { effectiveFps = player->effectiveFps(); realFps = player->realFps(); framesDropped = player->framesDroppedPortion(); isPlaying = player->isPlaying(); } KisConfig cfg; const bool value = cfg.animationDropFrames(); m_dropFramesAction->setIcon(value ? KisIconUtils::loadIcon(framesDropped > 0.05 ? "droppedframes" : "dropframe") : KisIconUtils::loadIcon("dropframe")); QString text; if (!isPlaying) { text = QString("%1 (%2)") .arg(KisAnimationUtils::dropFramesActionName) .arg(KritaUtils::toLocalizedOnOff(value)); } else { text = QString("%1 (%2)\n" "%3\n" "%4\n" "%5") .arg(KisAnimationUtils::dropFramesActionName) .arg(KritaUtils::toLocalizedOnOff(value)) .arg(i18n("Effective FPS:\t%1", effectiveFps)) .arg(i18n("Real FPS:\t%1", realFps)) .arg(i18n("Frames dropped:\t%1\%", framesDropped * 100)); } m_dropFramesAction->setText(text); } void AnimationDocker::slotUpdateIcons() { m_previousFrameAction->setIcon(KisIconUtils::loadIcon("prevframe")); m_nextFrameAction->setIcon(KisIconUtils::loadIcon("nextframe")); m_previousKeyFrameAction->setIcon(KisIconUtils::loadIcon("prevkeyframe")); m_nextKeyFrameAction->setIcon(KisIconUtils::loadIcon("nextkeyframe")); m_firstFrameAction->setIcon(KisIconUtils::loadIcon("firstframe")); m_lastFrameAction->setIcon(KisIconUtils::loadIcon("lastframe")); updatePlayPauseIcon(); m_addBlankFrameAction->setIcon(KisIconUtils::loadIcon("addblankframe")); m_addDuplicateFrameAction->setIcon(KisIconUtils::loadIcon("addduplicateframe")); m_deleteKeyframeAction->setIcon(KisIconUtils::loadIcon("deletekeyframe")); updateLazyFrameIcon(); updateDropFramesIcon(); m_animationWidget->btnOnionSkinOptions->setIcon(KisIconUtils::loadIcon("onion_skin_options")); m_animationWidget->btnOnionSkinOptions->setIconSize(QSize(22, 22)); m_animationWidget->btnNextKeyFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnPreviousKeyFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnFirstFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnLastFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnPreviousFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnPlay->setIconSize(QSize(22, 22)); m_animationWidget->btnNextFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnAddKeyframe->setIconSize(QSize(22, 22)); m_animationWidget->btnAddDuplicateFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnDeleteKeyframe->setIconSize(QSize(22, 22)); m_animationWidget->btnLazyFrame->setIconSize(QSize(22, 22)); m_animationWidget->btnDropFrames->setIconSize(QSize(22, 22)); } void AnimationDocker::slotLazyFrameChanged(bool value) { KisImageConfig cfg; if (value != cfg.lazyFrameCreationEnabled()) { cfg.setLazyFrameCreationEnabled(value); updateLazyFrameIcon(); } } void AnimationDocker::slotDropFramesChanged(bool value) { KisConfig cfg; if (value != cfg.animationDropFrames()) { cfg.setAnimationDropFrames(value); updateDropFramesIcon(); } } void AnimationDocker::slotCurrentNodeChanged(KisNodeSP node) { bool isNodeAnimatable = false; m_newKeyframeMenu->clear(); m_deleteKeyframeMenu->clear(); if (!node.isNull()) { if (KisAnimationUtils::supportsContentFrames(node)) { isNodeAnimatable = true; m_newKeyframeMenu->addAction(m_addBlankFrameAction); m_deleteKeyframeMenu->addAction(m_deleteKeyframeAction); } if (node->inherits("KisLayer")) { isNodeAnimatable = true; m_newKeyframeMenu->addAction(m_addOpacityKeyframeAction); m_deleteKeyframeMenu->addAction(m_deleteOpacityKeyframeAction); } /* if (node->inherits("KisTransformMask")) { isNodeAnimatable = true; m_newKeyframeMenu->addAction(m_addTransformKeyframeAction); m_deleteKeyframeMenu->addAction(m_deleteTransformKeyframeAction); } */ } m_animationWidget->btnAddKeyframe->setEnabled(isNodeAnimatable); m_animationWidget->btnAddDuplicateFrame->setEnabled(isNodeAnimatable); m_animationWidget->btnDeleteKeyframe->setEnabled(isNodeAnimatable); } void AnimationDocker::addKeyframe(const QString &channel, bool copy) { if (!m_canvas) return; KisNodeSP node = m_canvas->viewManager()->activeNode(); if (!node) return; const int time = m_canvas->image()->animationInterface()->currentTime(); KisAnimationUtils::createKeyframeLazy(m_canvas->image(), node, channel, time, copy); } void AnimationDocker::deleteKeyframe(const QString &channel) { if (!m_canvas) return; KisNodeSP node = m_canvas->viewManager()->activeNode(); if (!node) return; const int time = m_canvas->image()->animationInterface()->currentTime(); KisAnimationUtils::removeKeyframe(m_canvas->image(), node, channel, time); } diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index daed88e26f..dc5d4ef97e 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,645 +1,646 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_model.h" #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; KisDummiesFacadeBase *dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; QList channels = dummy->node()->keyframeChannels(); Q_FOREACH(KisKeyframeChannel *channel, channels) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodePropertyListCommand::setNodePropertiesNoUndo(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; return KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setUseInTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { return m_d->converter->dummyFromRow(index.row())->node(); } QList TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade) { m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); } if (m_d->dummiesFacade != oldDummiesFacade) { reset(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::processUpdateQueue() { Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); KIS_ASSERT_RECOVER_RETURN(dummy); m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case ColorLabel: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return QVariant(); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); + emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case ColorLabel: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedMask()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->useInTimeline(); } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setUseInTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = m_d->lastClickedIndex.row(); const int baseColumn = m_d->lastClickedIndex.column(); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { stream << index.row() - baseRow << index.column() - baseColumn; } data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; const bool copyFrames = action == Qt::CopyAction; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; QModelIndexList srcIndexes; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; int srcRow = baseRow + relRow; int srcColumn = baseColumn + relColumn; srcIndexes << index(srcRow, srcColumn); } const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); return offsetFrames(srcIndexes, offset, copyFrames); } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setUseInTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; bool result = m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); if (result) { emit dataChanged(dstIndex, dstIndex); } return result; } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; bool result = m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); if (result) { emit dataChanged(dstIndex, dstIndex); } return result; } diff --git a/plugins/dockers/animation/timeline_frames_model.h b/plugins/dockers/animation/timeline_frames_model.h index 3c147ad8a8..67a74a5be5 100644 --- a/plugins/dockers/animation/timeline_frames_model.h +++ b/plugins/dockers/animation/timeline_frames_model.h @@ -1,121 +1,122 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_MODEL_H #define __TIMELINE_FRAMES_MODEL_H #include #include #include "kritaanimationdocker_export.h" #include "kis_node_model.h" #include "kis_types.h" #include "kis_node.h" #include "timeline_node_list_keeper.h" class KisNodeDummy; class KisDummiesFacadeBase; class KisAnimationPlayer; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesModel : public TimelineNodeListKeeper::ModelWithExternalNotifications { Q_OBJECT public: TimelineFramesModel(QObject *parent); ~TimelineFramesModel(); bool hasConnectionToCanvas() const; void setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image); bool canDropFrameData(const QMimeData *data, const QModelIndex &index); bool insertOtherLayer(int index, int dstRow); int activeLayerRow() const; bool createFrame(const QModelIndex &dstIndex); bool copyFrame(const QModelIndex &dstIndex); void setLastClickedIndex(const QModelIndex &index); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; bool setData(const QModelIndex &index, const QVariant &value, int role); QVariant headerData(int section, Qt::Orientation orientation, int role) const; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role); Qt::DropActions supportedDragActions() const; Qt::DropActions supportedDropActions() const; QStringList mimeTypes() const; QMimeData * mimeData(const QModelIndexList &indexes) const; bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent); Qt::ItemFlags flags(const QModelIndex &index) const; bool insertRows(int row, int count, const QModelIndex &parent); bool removeRows(int row, int count, const QModelIndex &parent); enum ItemDataRole { ActiveLayerRole = KisTimeBasedItemModel::UserRole, TimelinePropertiesRole, OtherLayersRole, LayerUsedInTimelineRole, ColorLabel }; // metatype is added by the original implementation typedef KisBaseNode::Property Property; typedef KisBaseNode::PropertyList PropertyList; typedef TimelineNodeListKeeper::OtherLayer OtherLayer; typedef TimelineNodeListKeeper::OtherLayersList OtherLayersList; struct NodeManipulationInterface { virtual ~NodeManipulationInterface() {}; virtual KisLayerSP addPaintLayer() const = 0; virtual void removeNode(KisNodeSP node) const = 0; }; /** * NOTE: the model has an ownership over the interface, that is it'll * be deleted automatically later */ void setNodeManipulationInterface(NodeManipulationInterface *iface); protected: KisNodeSP nodeAt(QModelIndex index) const; QList channelsAt(QModelIndex index) const; private Q_SLOTS: void slotDummyChanged(KisNodeDummy *dummy); void processUpdateQueue(); public Q_SLOTS: void slotCurrentNodeChanged(KisNodeSP node); Q_SIGNALS: void requestCurrentNodeChanged(KisNodeSP node); void sigInfiniteTimelineUpdateNeeded(); + void sigEnsureRowVisible(int row); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_MODEL_H */ diff --git a/plugins/dockers/animation/timeline_frames_view.cpp b/plugins/dockers/animation/timeline_frames_view.cpp index e0c836ad6e..cf9dcbf308 100644 --- a/plugins/dockers/animation/timeline_frames_view.cpp +++ b/plugins/dockers/animation/timeline_frames_view.cpp @@ -1,905 +1,916 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_frames_view.h" #include "timeline_frames_model.h" #include "timeline_ruler_header.h" #include "timeline_layers_header.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "timeline_frames_item_delegate.h" #include "kis_zoom_button.h" #include "kis_icon_utils.h" #include "kis_animation_utils.h" #include "kis_custom_modifiers_catcher.h" #include "kis_action.h" #include "kis_signal_compressor.h" #include "kis_time_range.h" #include "kis_color_label_selector_widget.h" typedef QPair QItemViewPaintPair; typedef QList QItemViewPaintPairs; struct TimelineFramesView::Private { Private(TimelineFramesView *_q) : q(_q), fps(1), zoomStillPointIndex(-1), zoomStillPointOriginalOffset(0), dragInProgress(false), dragWasSuccessful(false), modifiersCatcher(0), selectionChangedCompressor(300, KisSignalCompressor::FIRST_INACTIVE) {} TimelineFramesView *q; TimelineFramesModel *model; TimelineRulerHeader *horizontalRuler; TimelineLayersHeader *layersHeader; int fps; int zoomStillPointIndex; int zoomStillPointOriginalOffset; QPoint initialDragPanValue; QPoint initialDragPanPos; QToolButton *addLayersButton; KisAction *showHideLayerAction; KisColorLabelSelectorWidget *colorSelector; QWidgetAction *colorSelectorAction; KisColorLabelSelectorWidget *multiframeColorSelector; QWidgetAction *multiframeColorSelectorAction; QMenu *layerEditingMenu; QMenu *existingLayersMenu; QMenu *frameCreationMenu; QMenu *frameEditingMenu; QMenu *multipleFrameEditingMenu; QMap globalActions; KisZoomButton *zoomDragButton; bool dragInProgress; bool dragWasSuccessful; KisCustomModifiersCatcher *modifiersCatcher; QPoint lastPressedPosition; KisSignalCompressor selectionChangedCompressor; QStyleOptionViewItem viewOptionsV4() const; QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const; QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const; }; TimelineFramesView::TimelineFramesView(QWidget *parent) : QTableView(parent), m_d(new Private(this)) { m_d->modifiersCatcher = new KisCustomModifiersCatcher(this); m_d->modifiersCatcher->addModifier("pan-zoom", Qt::Key_Space); m_d->modifiersCatcher->addModifier("offset-frame", Qt::Key_Alt); setCornerButtonEnabled(false); setSelectionBehavior(QAbstractItemView::SelectItems); setSelectionMode(QAbstractItemView::ExtendedSelection); setItemDelegate(new TimelineFramesItemDelegate(this)); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); m_d->horizontalRuler = new TimelineRulerHeader(this); this->setHorizontalHeader(m_d->horizontalRuler); m_d->layersHeader = new TimelineLayersHeader(this); m_d->layersHeader->setSectionResizeMode(QHeaderView::Fixed); m_d->layersHeader->setDefaultSectionSize(24); m_d->layersHeader->setMinimumWidth(60); m_d->layersHeader->setHighlightSections(true); this->setVerticalHeader(m_d->layersHeader); connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(slotUpdateInfiniteFramesCount())); connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(slotUpdateInfiniteFramesCount())); m_d->addLayersButton = new QToolButton(this); m_d->addLayersButton->setAutoRaise(true); m_d->addLayersButton->setIcon(KisIconUtils::loadIcon("addlayer")); m_d->addLayersButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_d->addLayersButton->setPopupMode(QToolButton::InstantPopup); m_d->layerEditingMenu = new QMenu(this); m_d->layerEditingMenu->addAction(KisAnimationUtils::newLayerActionName, this, SLOT(slotAddNewLayer())); m_d->existingLayersMenu = m_d->layerEditingMenu->addMenu(KisAnimationUtils::addExistingLayerActionName); m_d->layerEditingMenu->addSeparator(); m_d->showHideLayerAction = new KisAction(KisAnimationUtils::showLayerActionName, this); m_d->showHideLayerAction->setActivationFlags(KisAction::ACTIVE_LAYER); connect(m_d->showHideLayerAction, SIGNAL(triggered()), SLOT(slotHideLayerFromTimeline())); m_d->showHideLayerAction->setCheckable(true); m_d->globalActions.insert("show_in_timeline", m_d->showHideLayerAction); m_d->layerEditingMenu->addAction(m_d->showHideLayerAction); m_d->layerEditingMenu->addAction(KisAnimationUtils::removeLayerActionName, this, SLOT(slotRemoveLayer())); connect(m_d->existingLayersMenu, SIGNAL(aboutToShow()), SLOT(slotUpdateLayersMenu())); connect(m_d->existingLayersMenu, SIGNAL(triggered(QAction*)), SLOT(slotAddExistingLayer(QAction*))); connect(m_d->layersHeader, SIGNAL(sigRequestContextMenu(const QPoint&)), SLOT(slotLayerContextMenuRequested(const QPoint&))); m_d->addLayersButton->setMenu(m_d->layerEditingMenu); m_d->frameCreationMenu = new QMenu(this); m_d->frameCreationMenu->addAction(KisAnimationUtils::addFrameActionName, this, SLOT(slotNewFrame())); m_d->frameCreationMenu->addAction(KisAnimationUtils::duplicateFrameActionName, this, SLOT(slotCopyFrame())); m_d->colorSelector = new KisColorLabelSelectorWidget(this); m_d->colorSelectorAction = new QWidgetAction(this); m_d->colorSelectorAction->setDefaultWidget(m_d->colorSelector); connect(m_d->colorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->frameEditingMenu = new QMenu(this); m_d->frameEditingMenu->addAction(KisAnimationUtils::removeFrameActionName, this, SLOT(slotRemoveFrame())); m_d->frameEditingMenu->addAction(m_d->colorSelectorAction); m_d->multiframeColorSelector = new KisColorLabelSelectorWidget(this); m_d->multiframeColorSelectorAction = new QWidgetAction(this); m_d->multiframeColorSelectorAction->setDefaultWidget(m_d->multiframeColorSelector); connect(m_d->multiframeColorSelector, &KisColorLabelSelectorWidget::currentIndexChanged, this, &TimelineFramesView::slotColorLabelChanged); m_d->multipleFrameEditingMenu = new QMenu(this); m_d->multipleFrameEditingMenu->addAction(KisAnimationUtils::removeFramesActionName, this, SLOT(slotRemoveFrame())); m_d->multipleFrameEditingMenu->addAction(m_d->multiframeColorSelectorAction); m_d->zoomDragButton = new KisZoomButton(this); m_d->zoomDragButton->setAutoRaise(true); m_d->zoomDragButton->setIcon(KisIconUtils::loadIcon("zoom-horizontal")); m_d->zoomDragButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_d->zoomDragButton->setToolTip(i18nc("@info:tooltip", "Zoom Timeline. Hold down and drag left or right.")); m_d->zoomDragButton->setPopupMode(QToolButton::InstantPopup); connect(m_d->zoomDragButton, SIGNAL(zoomLevelChanged(qreal)), SLOT(slotZoomButtonChanged(qreal))); connect(m_d->zoomDragButton, SIGNAL(zoomStarted(qreal)), SLOT(slotZoomButtonPressed(qreal))); setFramesPerSecond(12); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); connect(&m_d->selectionChangedCompressor, SIGNAL(timeout()), SLOT(slotSelectionChanged())); } TimelineFramesView::~TimelineFramesView() { } QMap TimelineFramesView::globalActions() const { return m_d->globalActions; } void resizeToMinimalSize(QAbstractButton *w, int minimalSize) { QSize buttonSize = w->sizeHint(); if (buttonSize.height() > minimalSize) { buttonSize = QSize(minimalSize, minimalSize); } w->resize(buttonSize); } void TimelineFramesView::updateGeometries() { QTableView::updateGeometries(); const int availableHeight = m_d->horizontalRuler->height(); const int margin = 2; const int minimalSize = availableHeight - 2 * margin; resizeToMinimalSize(m_d->addLayersButton, minimalSize); resizeToMinimalSize(m_d->zoomDragButton, minimalSize); int x = 2 * margin; int y = (availableHeight - minimalSize) / 2; m_d->addLayersButton->move(x, 2 * y); const int availableWidth = m_d->layersHeader->width(); x = availableWidth - margin - minimalSize; m_d->zoomDragButton->move(x, 2 * y); } void TimelineFramesView::setModel(QAbstractItemModel *model) { TimelineFramesModel *framesModel = qobject_cast(model); m_d->model = framesModel; QTableView::setModel(model); connect(m_d->model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), this, SLOT(slotHeaderDataChanged(Qt::Orientation, int, int))); connect(m_d->model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); connect(m_d->model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(slotReselectCurrentIndex())); connect(m_d->model, SIGNAL(sigInfiniteTimelineUpdateNeeded()), this, SLOT(slotUpdateInfiniteFramesCount())); connect(selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)), &m_d->selectionChangedCompressor, SLOT(start())); + + connect(m_d->model, SIGNAL(sigEnsureRowVisible(int)), SLOT(slotEnsureRowVisible(int))); } void TimelineFramesView::setFramesPerSecond(int fps) { m_d->fps = fps; m_d->horizontalRuler->setFramePerSecond(fps); // For some reason simple update sometimes doesn't work here, so // reset the whole header // // m_d->horizontalRuler->reset(); } void TimelineFramesView::slotZoomButtonPressed(qreal staticPoint) { m_d->zoomStillPointIndex = qIsNaN(staticPoint) ? currentIndex().column() : staticPoint; const int w = m_d->horizontalRuler->defaultSectionSize(); m_d->zoomStillPointOriginalOffset = w * m_d->zoomStillPointIndex - horizontalScrollBar()->value(); } void TimelineFramesView::slotZoomButtonChanged(qreal zoomLevel) { if (m_d->horizontalRuler->setZoom(zoomLevel)) { slotUpdateInfiniteFramesCount(); const int w = m_d->horizontalRuler->defaultSectionSize(); horizontalScrollBar()->setValue(w * m_d->zoomStillPointIndex - m_d->zoomStillPointOriginalOffset); viewport()->update(); } } void TimelineFramesView::slotColorLabelChanged(int label) { Q_FOREACH(QModelIndex index, selectedIndexes()) { m_d->model->setData(index, label, TimelineFramesModel::ColorLabel); } KisImageConfig config; config.setDefaultFrameColorLabel(label); } void TimelineFramesView::slotUpdateInfiniteFramesCount() { if (horizontalScrollBar()->isSliderDown()) return; const int sectionWidth = m_d->horizontalRuler->defaultSectionSize(); const int calculatedIndex = (horizontalScrollBar()->value() + m_d->horizontalRuler->width() - 1) / sectionWidth; m_d->model->setLastVisibleFrame(calculatedIndex); } void TimelineFramesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTableView::currentChanged(current, previous); if (previous.column() != current.column()) { m_d->model->setData(previous, false, TimelineFramesModel::ActiveFrameRole); m_d->model->setData(current, true, TimelineFramesModel::ActiveFrameRole); } } QItemSelectionModel::SelectionFlags TimelineFramesView::selectionCommand(const QModelIndex &index, const QEvent *event) const { // WARNING: Copy-pasted from KisNodeView! Please keep in sync! /** * 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; } } return QAbstractItemView::selectionCommand(index, event); } void TimelineFramesView::slotSelectionChanged() { int minColumn = std::numeric_limits::max(); int maxColumn = std::numeric_limits::min(); foreach (const QModelIndex &idx, selectedIndexes()) { if (idx.column() > maxColumn) { maxColumn = idx.column(); } if (idx.column() < minColumn) { minColumn = idx.column(); } } KisTimeRange range; if (maxColumn > minColumn) { range = KisTimeRange(minColumn, maxColumn - minColumn + 1); } m_d->model->setPlaybackRange(range); } void TimelineFramesView::slotReselectCurrentIndex() { QModelIndex index = currentIndex(); currentChanged(index, index); } +void TimelineFramesView::slotEnsureRowVisible(int row) +{ + QModelIndex index = currentIndex(); + if (!index.isValid() || row < 0) return; + + index = m_d->model->index(row, index.column()); + scrollTo(index); +} + void TimelineFramesView::slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (m_d->model->isPlaybackActive()) return; int selectedColumn = -1; for (int j = topLeft.column(); j <= bottomRight.column(); j++) { QVariant value = m_d->model->data( m_d->model->index(topLeft.row(), j), TimelineFramesModel::ActiveFrameRole); if (value.isValid() && value.toBool()) { selectedColumn = j; break; } } QModelIndex index = currentIndex(); if (!index.isValid() && selectedColumn < 0) { return; } if (selectedColumn == -1) { selectedColumn = index.column(); } if (selectedColumn != index.column() && !m_d->dragInProgress) { int row= index.isValid() ? index.row() : 0; setCurrentIndex(m_d->model->index(row, selectedColumn)); } } void TimelineFramesView::slotHeaderDataChanged(Qt::Orientation orientation, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (orientation == Qt::Horizontal) { const int newFps = m_d->model->headerData(0, Qt::Horizontal, TimelineFramesModel::FramesPerSecondRole).toInt(); if (newFps != m_d->fps) { setFramesPerSecond(newFps); } } else /* if (orientation == Qt::Vertical) */ { updateShowInTimeline(); } } void TimelineFramesView::rowsInserted(const QModelIndex& parent, int start, int end) { QTableView::rowsInserted(parent, start, end); updateShowInTimeline(); } inline bool isIndexDragEnabled(QAbstractItemModel *model, const QModelIndex &index) { return (model->flags(index) & Qt::ItemIsDragEnabled); } QStyleOptionViewItem TimelineFramesView::Private::viewOptionsV4() const { QStyleOptionViewItem option = q->viewOptions(); option.locale = q->locale(); option.locale.setNumberOptions(QLocale::OmitGroupSeparator); option.widget = q; return option; } QItemViewPaintPairs TimelineFramesView::Private::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QRect &rect = *r; const QRect viewportRect = q->viewport()->rect(); QItemViewPaintPairs ret; for (int i = 0; i < indexes.count(); ++i) { const QModelIndex &index = indexes.at(i); const QRect current = q->visualRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); rect |= current; } } rect &= viewportRect; return ret; } QPixmap TimelineFramesView::Private::renderToPixmap(const QModelIndexList &indexes, QRect *r) const { Q_ASSERT(r); QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); if (paintPairs.isEmpty()) return QPixmap(); QPixmap pixmap(r->size()); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); QStyleOptionViewItem option = viewOptionsV4(); option.state |= QStyle::State_Selected; for (int j = 0; j < paintPairs.count(); ++j) { option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; //adjustViewOptionsForIndex(&option, current); q->itemDelegate(current)->paint(&painter, option, current); } return pixmap; } void TimelineFramesView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty() && m_d->modifiersCatcher->modifierPressed("offset-frame")) { QVector rows; int leftmostColumn = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, indexes) { leftmostColumn = qMin(leftmostColumn, index.column()); if (!rows.contains(index.row())) { rows.append(index.row()); } } const int lastColumn = m_d->model->columnCount() - 1; selectionModel()->clear(); Q_FOREACH (const int row, rows) { QItemSelection sel(m_d->model->index(row, leftmostColumn), m_d->model->index(row, lastColumn)); selectionModel()->select(sel, QItemSelectionModel::Select); } supportedActions = Qt::MoveAction; { QModelIndexList indexes = selectedIndexes(); for(int i = indexes.count() - 1 ; i >= 0; --i) { if (!isIndexDragEnabled(m_d->model, indexes.at(i))) indexes.removeAt(i); } selectionModel()->clear(); if (indexes.count() > 0) { QMimeData *data = m_d->model->mimeData(indexes); if (!data) return; QRect rect; QPixmap pixmap = m_d->renderToPixmap(indexes, &rect); rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); drag->setHotSpot(m_d->lastPressedPosition - rect.topLeft()); drag->exec(supportedActions, Qt::MoveAction); setCurrentIndex(currentIndex()); } } } else { /** * Workaround for Qt5's bugs: * * 1) Qt doesn't treat selection the selection on D&D * correctly, so we save it in advance and restore * afterwards. * * 2) There is a private variable in QAbstractItemView: * QAbstractItemView::Private::currentSelectionStartIndex. * It is initialized *only* when the setCurrentIndex() is called * explicitly on the view object, not on the selection model. * Therefore we should explicitly call setCurrentIndex() after * D&D, even if it already has *correct* value! * * 2) We should also call selectionModel()->select() * explicitly. There are two reasons for it: 1) Qt doesn't * maintain selection over D&D; 2) when reselecting single * element after D&D, Qt goes crazy, because it tries to * read *global* keyboard modifiers. Therefore if we are * dragging with Shift or Ctrl pressed it'll get crazy. So * just reset it explicitly. */ QModelIndexList selectionBefore = selectionModel()->selectedIndexes(); QModelIndex currentBefore = selectionModel()->currentIndex(); // initialize a global status variable m_d->dragWasSuccessful = false; QAbstractItemView::startDrag(supportedActions); QModelIndex newCurrent; QPoint selectionOffset; if (m_d->dragWasSuccessful) { newCurrent = currentIndex(); selectionOffset = QPoint(newCurrent.column() - currentBefore.column(), newCurrent.row() - currentBefore.row()); } else { newCurrent = currentBefore; selectionOffset = QPoint(); } setCurrentIndex(newCurrent); selectionModel()->clearSelection(); Q_FOREACH (const QModelIndex &idx, selectionBefore) { QModelIndex newIndex = model()->index(idx.row() + selectionOffset.y(), idx.column() + selectionOffset.x()); selectionModel()->select(newIndex, QItemSelectionModel::Select); } } } void TimelineFramesView::dragEnterEvent(QDragEnterEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragEnterEvent(event); } void TimelineFramesView::dragMoveEvent(QDragMoveEvent *event) { m_d->dragInProgress = true; m_d->model->setScrubState(true); QTableView::dragMoveEvent(event); if (event->isAccepted()) { QModelIndex index = indexAt(event->pos()); if (!m_d->model->canDropFrameData(event->mimeData(), index)) { event->ignore(); } else { selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } } } void TimelineFramesView::dropEvent(QDropEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dropEvent(event); m_d->dragWasSuccessful = event->isAccepted(); } void TimelineFramesView::dragLeaveEvent(QDragLeaveEvent *event) { m_d->dragInProgress = false; m_d->model->setScrubState(false); QAbstractItemView::dragLeaveEvent(event); } void TimelineFramesView::mousePressEvent(QMouseEvent *event) { QPersistentModelIndex index = indexAt(event->pos()); if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (event->button() == Qt::RightButton) { // TODO: try calculate index under mouse cursor even when // it is outside any visible row qreal staticPoint = index.isValid() ? index.column() : currentIndex().column(); m_d->zoomDragButton->beginZoom(event->pos(), staticPoint); } else if (event->button() == Qt::LeftButton) { m_d->initialDragPanPos = event->pos(); m_d->initialDragPanValue = QPoint(horizontalScrollBar()->value(), verticalScrollBar()->value()); } event->accept(); } else if (event->button() == Qt::RightButton) { int numSelectedItems = selectionModel()->selectedIndexes().size(); if (index.isValid() && numSelectedItems <= 1 && m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { model()->setData(index, true, TimelineFramesModel::ActiveLayerRole); model()->setData(index, true, TimelineFramesModel::ActiveFrameRole); setCurrentIndex(index); if (model()->data(index, TimelineFramesModel::FrameExistsRole).toBool() || model()->data(index, TimelineFramesModel::SpecialKeyframeExists).toBool()) { { KisSignalsBlocker b(m_d->colorSelector); QVariant colorLabel = index.data(TimelineFramesModel::ColorLabel); int labelIndex = colorLabel.isValid() ? colorLabel.toInt() : 0; m_d->colorSelector->setCurrentIndex(labelIndex); } m_d->frameEditingMenu->exec(event->globalPos()); } else { m_d->frameCreationMenu->exec(event->globalPos()); } } else if (numSelectedItems > 1) { int labelIndex = 0; bool haveFrames = false; Q_FOREACH(QModelIndex index, selectedIndexes()) { haveFrames |= index.data(TimelineFramesModel::FrameExistsRole).toBool(); QVariant colorLabel = index.data(TimelineFramesModel::ColorLabel); if (colorLabel.isValid()) { if (labelIndex == 0) { labelIndex = colorLabel.toInt(); } else { labelIndex = 0; break; } } } if (!haveFrames) { m_d->multiframeColorSelectorAction->setVisible(false); } else { KisSignalsBlocker b(m_d->multiframeColorSelector); m_d->multiframeColorSelector->setCurrentIndex(labelIndex); m_d->multiframeColorSelectorAction->setVisible(true); } m_d->multipleFrameEditingMenu->exec(event->globalPos()); } } else { if (index.isValid()) { m_d->model->setLastClickedIndex(index); } m_d->lastPressedPosition = QPoint(horizontalOffset(), verticalOffset()) + event->pos(); QAbstractItemView::mousePressEvent(event); } } void TimelineFramesView::mouseMoveEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { if (e->buttons() & Qt::RightButton) { m_d->zoomDragButton->continueZoom(e->pos()); } else if (e->buttons() & Qt::LeftButton) { QPoint diff = e->pos() - m_d->initialDragPanPos; QPoint offset = QPoint(m_d->initialDragPanValue.x() - diff.x(), m_d->initialDragPanValue.y() - diff.y()); const int height = m_d->layersHeader->defaultSectionSize(); horizontalScrollBar()->setValue(offset.x()); verticalScrollBar()->setValue(offset.y() / height); } e->accept(); } else { m_d->model->setScrubState(true); QTableView::mouseMoveEvent(e); } } void TimelineFramesView::mouseReleaseEvent(QMouseEvent *e) { if (m_d->modifiersCatcher->modifierPressed("pan-zoom")) { e->accept(); } else { m_d->model->setScrubState(false); QTableView::mouseReleaseEvent(e); } } void TimelineFramesView::wheelEvent(QWheelEvent *e) { QModelIndex index = currentIndex(); int column= -1; if (index.isValid()) { column= index.column() + ((e->delta() > 0) ? 1 : -1); } if (column >= 0 && !m_d->dragInProgress) { setCurrentIndex(m_d->model->index(index.row(), column)); } } void TimelineFramesView::slotUpdateLayersMenu() { QAction *action = 0; m_d->existingLayersMenu->clear(); QVariant value = model()->headerData(0, Qt::Vertical, TimelineFramesModel::OtherLayersRole); if (value.isValid()) { TimelineFramesModel::OtherLayersList list = value.value(); int i = 0; Q_FOREACH (const TimelineFramesModel::OtherLayer &l, list) { action = m_d->existingLayersMenu->addAction(l.name); action->setData(i++); } } } void TimelineFramesView::slotLayerContextMenuRequested(const QPoint &globalPos) { m_d->layerEditingMenu->exec(globalPos); } void TimelineFramesView::updateShowInTimeline() { const int row = m_d->model->activeLayerRow(); const bool status = m_d->model->headerData(row, Qt::Vertical, TimelineFramesModel::LayerUsedInTimelineRole).toBool(); m_d->showHideLayerAction->setChecked(status); } void TimelineFramesView::slotAddNewLayer() { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() : 0; model()->insertRow(newRow); } void TimelineFramesView::slotAddExistingLayer(QAction *action) { QVariant value = action->data(); if (value.isValid()) { QModelIndex index = currentIndex(); const int newRow = index.isValid() ? index.row() + 1 : 0; m_d->model->insertOtherLayer(value.toInt(), newRow); } } void TimelineFramesView::slotRemoveLayer() { QModelIndex index = currentIndex(); if (!index.isValid()) return; model()->removeRow(index.row()); } void TimelineFramesView::slotHideLayerFromTimeline() { const int row = m_d->model->activeLayerRow(); const bool status = m_d->model->headerData(row, Qt::Vertical, TimelineFramesModel::LayerUsedInTimelineRole).toBool(); m_d->model->setHeaderData(row, Qt::Vertical, !status, TimelineFramesModel::LayerUsedInTimelineRole); } void TimelineFramesView::slotNewFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->createFrame(index); } void TimelineFramesView::slotCopyFrame() { QModelIndex index = currentIndex(); if (!index.isValid() || !m_d->model->data(index, TimelineFramesModel::FrameEditableRole).toBool()) { return; } m_d->model->copyFrame(index); } void TimelineFramesView::slotRemoveFrame() { QModelIndexList indexes = selectionModel()->selectedIndexes(); for (auto it = indexes.begin(); it != indexes.end(); /*noop*/) { if (!m_d->model->data(*it, TimelineFramesModel::FrameEditableRole).toBool()) { it = indexes.erase(it); } else { ++it; } } if (!indexes.isEmpty()) { m_d->model->removeFrames(indexes); } } diff --git a/plugins/dockers/animation/timeline_frames_view.h b/plugins/dockers/animation/timeline_frames_view.h index 1f39521614..4a77116edc 100644 --- a/plugins/dockers/animation/timeline_frames_view.h +++ b/plugins/dockers/animation/timeline_frames_view.h @@ -1,100 +1,102 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_FRAMES_VIEW_H #define __TIMELINE_FRAMES_VIEW_H #include #include #include "kritaanimationdocker_export.h" class KisAction; class TimelineWidget; class KRITAANIMATIONDOCKER_EXPORT TimelineFramesView : public QTableView { Q_OBJECT public: TimelineFramesView(QWidget *parent); ~TimelineFramesView(); void setModel(QAbstractItemModel *model); void updateGeometries(); QMap globalActions() const; public Q_SLOTS: void slotSelectionChanged(); private Q_SLOTS: void slotUpdateLayersMenu(); void slotAddNewLayer(); void slotAddExistingLayer(QAction *action); void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void slotRemoveLayer(); void slotHideLayerFromTimeline(); void slotLayerContextMenuRequested(const QPoint &globalPos); void slotNewFrame(); void slotCopyFrame(); void slotRemoveFrame(); void slotReselectCurrentIndex(); void slotUpdateInfiniteFramesCount(); void slotHeaderDataChanged(Qt::Orientation orientation, int first, int last); void slotZoomButtonPressed(qreal staticPoint); void slotZoomButtonChanged(qreal value); void slotColorLabelChanged(int); + void slotEnsureRowVisible(int row); + private: void setFramesPerSecond(int fps); void updateShowInTimeline(); protected: QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const; void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void startDrag(Qt::DropActions supportedActions); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); void dragLeaveEvent(QDragLeaveEvent *event); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void wheelEvent(QWheelEvent *e); void rowsInserted(const QModelIndex& parent, int start, int end); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_FRAMES_VIEW_H */ diff --git a/plugins/dockers/animation/timeline_layers_header.cpp b/plugins/dockers/animation/timeline_layers_header.cpp index ba1a85787b..c789298bb1 100644 --- a/plugins/dockers/animation/timeline_layers_header.cpp +++ b/plugins/dockers/animation/timeline_layers_header.cpp @@ -1,255 +1,250 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "timeline_layers_header.h" #include "kis_debug.h" #include #include #include #include "timeline_frames_model.h" #include "timeline_color_scheme.h" struct TimelineLayersHeader::Private { Private(TimelineLayersHeader *_q) : q(_q) {} TimelineLayersHeader *q; int numIcons(int logicalIndex) const; QRect iconRect(int logicalIndex, int iconIndex) const; int iconAt(int logicalIndex, const QPoint &pt); TimelineFramesModel::Property* getPropertyAt(TimelineFramesModel::PropertyList &props, int index); }; TimelineLayersHeader::TimelineLayersHeader(QWidget *parent) : QHeaderView(Qt::Vertical, parent), m_d(new Private(this)) { } TimelineLayersHeader::~TimelineLayersHeader() { } TimelineFramesModel::Property* TimelineLayersHeader::Private::getPropertyAt(TimelineFramesModel::PropertyList &props, int index) { int logical = 0; for (int i = 0; i < props.size(); i++) { if (props[i].isMutable) { if (logical == index) { return &props[i]; } logical++; } } return 0; } int TimelineLayersHeader::Private::numIcons(int logicalIndex) const { int result = 0; QVariant value = q->model()->headerData(logicalIndex, q->orientation(), TimelineFramesModel::TimelinePropertiesRole); if (value.isValid()) { TimelineFramesModel::PropertyList props = value.value(); Q_FOREACH (const TimelineFramesModel::Property &p, props) { if (p.isMutable) { result++; } } } return result; } QSize TimelineLayersHeader::sectionSizeFromContents(int logicalIndex) const { QSize baseSize = QHeaderView::sectionSizeFromContents(logicalIndex); baseSize.setWidth(baseSize.width() + 6 + (2 + 16) * m_d->numIcons(logicalIndex)); return baseSize; } QRect TimelineLayersHeader::Private::iconRect(int logicalIndex, int iconIndex) const { QSize sectionSize(q->viewport()->width(), q->sectionSize(logicalIndex)); const int iconWidth = 16; const int iconHeight = 16; const int y = (sectionSize.height() - iconHeight) / 2; const int x = sectionSize.width() - (numIcons(logicalIndex) - iconIndex) * (iconWidth + 2); return QRect(x, y, iconWidth, iconHeight); } void TimelineLayersHeader::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { painter->save(); QHeaderView::paintSection(painter, rect, logicalIndex); painter->restore(); bool isLayerActive = model()->headerData(logicalIndex, orientation(), TimelineFramesModel::ActiveLayerRole).toBool(); if (isLayerActive) { QColor lineColor = TimelineColorScheme::instance()->activeLayerBackground(); const int lineWidth = 2; QPen oldPen = painter->pen(); QBrush oldBrush(painter->brush()); painter->setPen(QPen(lineColor, lineWidth)); painter->setBrush(lineColor); const int x0 = rect.x(); const int y0 = rect.y(); const int x1 = rect.right(); const int y1 = rect.bottom(); QVector lines; lines << QLine(x0, y0 + lineWidth / 2, x1, y0 + lineWidth / 2); lines << QLine(x0, y1 - lineWidth / 2, x1, y1 - lineWidth / 2); painter->drawLines(lines); painter->setBrush(oldBrush); painter->setPen(oldPen); } QVariant value = model()->headerData(logicalIndex, orientation(), TimelineFramesModel::TimelinePropertiesRole); TimelineFramesModel::PropertyList props = value.value(); const int numIcons = m_d->numIcons(logicalIndex); for (int i = 0; i < numIcons; i++) { TimelineFramesModel::Property *p = m_d->getPropertyAt(props, i); const bool isActive = p->state.toBool(); QIcon icon = isActive ? p->onIcon : p->offIcon; QRect rc = m_d->iconRect(logicalIndex, i).translated(rect.topLeft()); icon.paint(painter, rc); } } int TimelineLayersHeader::Private::iconAt(int logicalIndex, const QPoint &pt) { QPoint sectionTopLeft(0, q->sectionViewportPosition(logicalIndex)); QPoint localPos = pt - sectionTopLeft; for (int i = 0; i < numIcons(logicalIndex); i++) { QRect rc = iconRect(logicalIndex, i); if (rc.contains(localPos)) { return i; } } return -1; } bool TimelineLayersHeader::viewportEvent(QEvent *e) { switch (e->type()) { case QEvent::ToolTip: { /** * Override tooltip if the cursor is over the properties icons. */ QHelpEvent *he = static_cast(e); int logical = logicalIndexAt(he->pos()); if (logical != -1) { const int iconIndex = m_d->iconAt(logical, he->pos()); if (iconIndex != -1) { QVariant value = model()->headerData(logical, orientation(), TimelineFramesModel::TimelinePropertiesRole); TimelineFramesModel::PropertyList props = value.value(); TimelineFramesModel::Property *p = m_d->getPropertyAt(props, iconIndex); QString text = QString("%1 (%2)") .arg(p->name) .arg(p->state.toBool() ? "on" : "off"); QToolTip::showText(he->globalPos(), text, this); return true; } } break; } default: break; } return QHeaderView::viewportEvent(e); } void TimelineLayersHeader::mousePressEvent(QMouseEvent *e) { int logical = logicalIndexAt(e->pos()); if (logical != -1) { const int iconIndex = m_d->iconAt(logical, e->pos()); if (iconIndex != -1) { QVariant value = model()->headerData(logical, orientation(), TimelineFramesModel::TimelinePropertiesRole); TimelineFramesModel::PropertyList props = value.value(); TimelineFramesModel::Property *p = m_d->getPropertyAt(props, iconIndex); bool currentState = p->state.toBool(); p->state = !currentState; value.setValue(props); model()->setHeaderData(logical, orientation(), value, TimelineFramesModel::TimelinePropertiesRole); return; } else if (e->button() == Qt::RightButton) { model()->setHeaderData(logical, orientation(), true, TimelineFramesModel::ActiveLayerRole); emit sigRequestContextMenu(e->globalPos()); return; } else if (e->button() == Qt::LeftButton) { - slotActivateSection(logical); + model()->setHeaderData(logical, orientation(), true, TimelineFramesModel::ActiveLayerRole); } } QHeaderView::mousePressEvent(e); } -void TimelineLayersHeader::slotActivateSection(int logicalIndex) -{ - model()->setHeaderData(logicalIndex, orientation(), true, TimelineFramesModel::ActiveLayerRole); -} - diff --git a/plugins/dockers/animation/timeline_layers_header.h b/plugins/dockers/animation/timeline_layers_header.h index 1e8869cb87..89b22dad10 100644 --- a/plugins/dockers/animation/timeline_layers_header.h +++ b/plugins/dockers/animation/timeline_layers_header.h @@ -1,51 +1,48 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __TIMELINE_LAYERS_HEADER_H #define __TIMELINE_LAYERS_HEADER_H #include #include class TimelineLayersHeader : public QHeaderView { Q_OBJECT public: TimelineLayersHeader(QWidget *parent); ~TimelineLayersHeader(); protected: QSize sectionSizeFromContents(int logicalIndex) const; void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; bool viewportEvent(QEvent *e); void mousePressEvent(QMouseEvent *e); -private Q_SLOTS: - void slotActivateSection(int logicalIndex); - Q_SIGNALS: void sigRequestContextMenu(const QPoint &pos); private: struct Private; const QScopedPointer m_d; }; #endif /* __TIMELINE_LAYERS_HEADER_H */ diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp index a76ff5de48..cc20c8eaee 100644 --- a/plugins/dockers/compositiondocker/compositiondocker_dock.cpp +++ b/plugins/dockers/compositiondocker/compositiondocker_dock.cpp @@ -1,296 +1,304 @@ /* * Copyright (c) 2012 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "compositiondocker_dock.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 "compositionmodel.h" CompositionDockerDock::CompositionDockerDock( ) : QDockWidget(i18n("Compositions")), m_canvas(0) { QWidget* widget = new QWidget(this); setupUi(widget); m_model = new CompositionModel(this); compositionView->setModel(m_model); compositionView->installEventFilter(this); deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); saveButton->setIcon(KisIconUtils::loadIcon("list-add")); exportButton->setIcon(KisIconUtils::loadIcon("document-export")); deleteButton->setToolTip(i18n("Delete Composition")); saveButton->setToolTip(i18n("New Composition")); exportButton->setToolTip(i18n("Export Composition")); setWidget(widget); connect( compositionView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(activated ( const QModelIndex & ) ) ); compositionView->setContextMenuPolicy(Qt::CustomContextMenu); connect( compositionView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); connect( deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteClicked())); connect( saveButton, SIGNAL(clicked(bool)), this, SLOT(saveClicked())); connect( exportButton, SIGNAL(clicked(bool)), this, SLOT(exportClicked())); saveNameEdit->setPlaceholderText(i18n("Insert Name")); - updateAction = new KisAction(i18n("Update Composition"), this); - updateAction->setObjectName("update_composition"); - connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition())); - renameAction = new KisAction(i18n("Rename Composition..."), this); - renameAction->setObjectName("rename_composition"); - connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition())); - m_actions.append(renameAction); } CompositionDockerDock::~CompositionDockerDock() { } void CompositionDockerDock::setCanvas(KoCanvasBase * canvas) { if (m_canvas && m_canvas->viewManager()) { Q_FOREACH (KisAction *action, m_actions) { m_canvas->viewManager()->actionManager()->takeAction(action); } } unsetCanvas(); setEnabled(canvas != 0); m_canvas = dynamic_cast(canvas); if (m_canvas && m_canvas->viewManager()) { - Q_FOREACH (KisAction *action, m_actions) { - m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action); + if (m_actions.isEmpty()) { + KisAction *updateAction = m_canvas->viewManager()->actionManager()->createAction("update_composition"); + connect(updateAction, SIGNAL(triggered()), this, SLOT(updateComposition())); + m_actions.append(updateAction); + + KisAction *renameAction = m_canvas->viewManager()->actionManager()->createAction("rename_composition"); + connect(renameAction, SIGNAL(triggered()), this, SLOT(renameComposition())); + m_actions.append(renameAction); + } else { + Q_FOREACH (KisAction *action, m_actions) { + m_canvas->viewManager()->actionManager()->addAction(action->objectName(), action); + } } updateModel(); } } void CompositionDockerDock::unsetCanvas() { setEnabled(false); m_canvas = 0; m_model->setCompositions(QList()); } void CompositionDockerDock::activated(const QModelIndex& index) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->apply(); } void CompositionDockerDock::deleteClicked() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); m_canvas->viewManager()->image()->removeComposition(composition); updateModel(); } } void CompositionDockerDock::saveClicked() { KisImageWSP image = m_canvas->viewManager()->image(); if (!image) return; // format as 001, 002 ... QString name = saveNameEdit->text(); if (name.isEmpty()) { bool found = false; int i = 1; do { name = QString("%1").arg(i, 3, 10, QChar('0')); found = false; Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (composition->name() == name) { found = true; break; } } i++; } while(found && i < 1000); } KisLayerCompositionSP composition(new KisLayerComposition(image, name)); composition->store(); image->addComposition(composition); saveNameEdit->clear(); updateModel(); compositionView->setCurrentIndex(m_model->index(image->compositions().count()-1, 0)); image->setModified(); } void CompositionDockerDock::updateModel() { if (m_model && m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { m_model->setCompositions(m_canvas->viewManager()->image()->compositions()); } } void CompositionDockerDock::exportClicked() { if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image()) { QString path; KoFileDialog dialog(0, KoFileDialog::OpenDirectory, "compositiondockerdock"); dialog.setCaption(i18n("Select a Directory")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation)); path = dialog.filename(); if (path.isNull()) return; if (!path.endsWith('/')) { path.append('/'); } KisImageWSP image = m_canvas->viewManager()->image(); QString filename = m_canvas->viewManager()->document()->localFilePath(); if (!filename.isEmpty()) { QFileInfo info(filename); path += info.baseName() + '_'; } Q_FOREACH (KisLayerCompositionSP composition, m_canvas->viewManager()->image()->compositions()) { if (!composition->isExportEnabled()) { continue; } composition->apply(); image->refreshGraph(); image->lock(); #if 0 image->rootLayer()->projection()->convertToQImage(0, 0, 0, image->width(), image->height()).save(path + composition->name() + ".png"); #else QRect r = image->bounds(); KisDocument *d = KisPart::instance()->createDocument(); KisImageWSP dst = new KisImage(d->createUndoStore(), r.width(), r.height(), image->colorSpace(), composition->name()); dst->setResolution(image->xRes(), image->yRes()); d->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, "projection", OPACITY_OPAQUE_U8); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->rootLayer()->projection(), r); dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0)); dst->refreshGraph(); d->setOutputMimeType("image/png"); d->setFileBatchMode(true); d->exportDocument(QUrl::fromLocalFile(path + composition->name() + ".png")); delete d; #endif image->unlock(); } } } bool CompositionDockerDock::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::KeyPress ) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { // new index will be set after the method is called QTimer::singleShot(0, this, SLOT(activateCurrentIndex())); } return false; } else { return QObject::eventFilter(obj, event); } } void CompositionDockerDock::activateCurrentIndex() { QModelIndex index = compositionView->currentIndex(); if (index.isValid()) { activated(index); } } void CompositionDockerDock::customContextMenuRequested(QPoint pos) { + if (m_actions.isEmpty()) return; + QMenu menu; - menu.addAction(updateAction); - menu.addAction(renameAction); + Q_FOREACH (KisAction *action, m_actions) { + menu.addAction(action); + + } menu.exec(compositionView->mapToGlobal(pos)); } void CompositionDockerDock::updateComposition() { QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); composition->store(); m_canvas->image()->setModified(); } } void CompositionDockerDock::renameComposition() { dbgKrita << "rename"; QModelIndex index = compositionView->currentIndex(); if (m_canvas && m_canvas->viewManager() && m_canvas->viewManager()->image() && index.isValid()) { KisLayerCompositionSP composition = m_model->compositionFromIndex(index); bool ok; QString name = QInputDialog::getText(this, i18n("Rename Composition"), i18n("New Name:"), QLineEdit::Normal, composition->name(), &ok); if (ok && !name.isEmpty()) { composition->setName(name); m_canvas->image()->setModified(); } } } diff --git a/plugins/dockers/compositiondocker/compositiondocker_dock.h b/plugins/dockers/compositiondocker/compositiondocker_dock.h index af670b7af7..2537051d85 100644 --- a/plugins/dockers/compositiondocker/compositiondocker_dock.h +++ b/plugins/dockers/compositiondocker/compositiondocker_dock.h @@ -1,64 +1,62 @@ /* * Copyright (c) 2012 Sven Langkamp * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 COMPOSITIONDOCKER_DOCK_H #define COMPOSITIONDOCKER_DOCK_H #include #include #include #include "ui_wdgcompositiondocker.h" class CompositionModel; class KisCanvas2; class KisAction; class CompositionDockerDock : public QDockWidget, public KoCanvasObserverBase, public Ui_WdgCompositionDocker { Q_OBJECT public: CompositionDockerDock(); ~CompositionDockerDock(); QString observerName() { return "CompositionDockerDock"; } virtual void setCanvas(KoCanvasBase *canvas); virtual void unsetCanvas(); void updateModel(); protected: bool eventFilter(QObject *obj, QEvent *event); private Q_SLOTS: void activated (const QModelIndex& index); void deleteClicked(); void saveClicked(); void exportClicked(); void activateCurrentIndex(); void customContextMenuRequested(QPoint pos); void updateComposition(); void renameComposition(); private: KisCanvas2 *m_canvas; CompositionModel *m_model; QVector m_actions; - KisAction* updateAction; - KisAction* renameAction; }; #endif diff --git a/plugins/dockers/historydocker/CMakeLists.txt b/plugins/dockers/historydocker/CMakeLists.txt index d89cec4573..ebb76efbe7 100644 --- a/plugins/dockers/historydocker/CMakeLists.txt +++ b/plugins/dockers/historydocker/CMakeLists.txt @@ -1,14 +1,15 @@ set(kritahistorydocker_SOURCES History.h History.cpp HistoryDock.h HistoryDock.cpp KisUndoView.cpp KisUndoView.h KisUndoModel.h KisUndoModel.cpp + DlgConfigureHistoryDock.cpp ) add_library(kritahistorydocker MODULE ${kritahistorydocker_SOURCES}) target_link_libraries(kritahistorydocker kritaimage kritaui) install(TARGETS kritahistorydocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/historydocker/DlgConfigureHistoryDock.cpp b/plugins/dockers/historydocker/DlgConfigureHistoryDock.cpp new file mode 100644 index 0000000000..136ec75f48 --- /dev/null +++ b/plugins/dockers/historydocker/DlgConfigureHistoryDock.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Boudewijn Rempt + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "DlgConfigureHistoryDock.h" + +#include +#include +#include +#include +#include + +DlgConfigureHistoryDock::DlgConfigureHistoryDock(KUndo2QStack *stack, QWidget *parent) + : KoDialog(parent) + , m_stack(stack) +{ + setButtons(KoDialog::Close); + + QWidget *page = new QWidget(this); + + QFormLayout *form = new QFormLayout(page); + QCheckBox *chkCumulative = new QCheckBox(i18n("Enable Cumulative Undo"), page); + chkCumulative->setChecked(stack->useCumulativeUndoRedo()); + connect(chkCumulative, SIGNAL(toggled(bool)), m_stack, SLOT(toggleCumulativeUndoRedo())); + + form->addRow(chkCumulative, new QWidget(page)); + + QLabel *l = new QLabel(i18n("Start merging time"), page); + QDoubleSpinBox *s = new KisDoubleParseSpinBox(page); + s->setToolTip(i18nc("@info:tooltip", "The amount of time after a merged stroke before merging again")); + s->setRange(3,10); + s->setValue(m_stack->timeT1()); + form->addRow(l, s); + s->setEnabled(chkCumulative->isChecked()); + connect(chkCumulative, SIGNAL(toggled(bool)), s, SLOT(setEnabled(bool))); + connect(s, SIGNAL(valueChanged(double)), m_stack, SLOT(setStackT1(double))); + + QLabel *l1 = new QLabel(i18n("Group time")); + QDoubleSpinBox *s1 = new KisDoubleParseSpinBox(); + s1->setToolTip(i18nc("@info:tooltip", "The amount of time every stroke should be apart from its previous stroke to be classified in one group")); + s1->setRange(0.3,s->value()); + s1->setValue(m_stack->timeT2()); + form->addRow(l1, s1); + s1->setEnabled(chkCumulative->isChecked()); + connect(chkCumulative, SIGNAL(toggled(bool)), s1, SLOT(setEnabled(bool))); + connect(s1, SIGNAL(valueChanged(double)), m_stack, SLOT(setStackT2(double))); + + QLabel *l2 = new QLabel(i18n("Split Strokes")); + QSpinBox *s2 = new KisIntParseSpinBox(); + s2->setToolTip(i18nc("@info:tooltip", "The number of last strokes which Krita should store separately")); + s2->setRange(1,m_stack->undoLimit()); + s2->setValue(m_stack->strokesN()); + form->addRow(l2, s2); + s2->setEnabled(chkCumulative->isChecked()); + connect(chkCumulative, SIGNAL(toggled(bool)), s2, SLOT(setEnabled(bool))); + connect(s2,SIGNAL(valueChanged(int)),SLOT(setStackN(int))); + + setMainWidget(page); +} diff --git a/plugins/dockers/historydocker/HistoryDock.h b/plugins/dockers/historydocker/DlgConfigureHistoryDock.h similarity index 50% copy from plugins/dockers/historydocker/HistoryDock.h copy to plugins/dockers/historydocker/DlgConfigureHistoryDock.h index 331c63bb41..2b5f8414e9 100644 --- a/plugins/dockers/historydocker/HistoryDock.h +++ b/plugins/dockers/historydocker/DlgConfigureHistoryDock.h @@ -1,51 +1,37 @@ -/* This file is part of the KDE project - * Copyright (C) 2010 Matus Talcik +/* + * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef _HISTORY_DOCK_H_ -#define _HISTORY_DOCK_H_ -#include -#include "KisUndoView.h" +#ifndef DLGCONFIGUREHISTORYDOCK_H +#define DLGCONFIGUREHISTORYDOCK_H -#include -#include +#include +#include +#include #include -#include -#include -#include "kis_types.h" -#include "kis_canvas2.h" -#include "KisViewManager.h" -#include "kis_image.h" -#include "kis_paint_device.h" - -class HistoryDock : public QDockWidget, public KoCanvasObserverBase +class DlgConfigureHistoryDock : public KoDialog { Q_OBJECT public: - HistoryDock(); - QString observerName() { return "HistoryDock"; } - virtual void setCanvas(KoCanvasBase *canvas); - virtual void unsetCanvas() { m_historyCanvas = 0; setEnabled(false); m_undoView->setStack(0); } + DlgConfigureHistoryDock(KUndo2QStack *stack, QWidget *parent = 0); private: - KisUndoView* m_undoView; - KoCanvasBase* m_historyCanvas; + KUndo2QStack *m_stack; }; - -#endif +#endif // DLGCONFIGUREHISTORYDOCK_H diff --git a/plugins/dockers/historydocker/HistoryDock.cpp b/plugins/dockers/historydocker/HistoryDock.cpp index 479ab5aaa8..d100ae4e33 100644 --- a/plugins/dockers/historydocker/HistoryDock.cpp +++ b/plugins/dockers/historydocker/HistoryDock.cpp @@ -1,50 +1,75 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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 "HistoryDock.h" #include #include +#include + +#include +#include +#include +#include +#include + +#include HistoryDock::HistoryDock() : QDockWidget() , m_historyCanvas(0) { + QWidget *page = new QWidget(this); + QVBoxLayout *vl = new QVBoxLayout(page); m_undoView = new KisUndoView(this); - setWidget(m_undoView); + vl->addWidget(m_undoView); + QHBoxLayout *hl = new QHBoxLayout(page); + hl->addSpacerItem(new QSpacerItem(10, 1, QSizePolicy::Expanding, QSizePolicy::Fixed)); + m_bnConfigure = new QToolButton(page); + m_bnConfigure->setIcon(KisIconUtils::loadIcon("configure")); + connect(m_bnConfigure, SIGNAL(clicked(bool)), SLOT(configure())); + hl->addWidget(m_bnConfigure); + vl->addItem(hl); + + setWidget(page); setWindowTitle(i18n("Undo History")); } void HistoryDock::setCanvas(KoCanvasBase *canvas) { setEnabled(canvas != 0); KisCanvas2* myCanvas = dynamic_cast( canvas ); if (myCanvas) { KUndo2Stack* undoStack = canvas->shapeController()->resourceManager()->undoStack(); m_undoView->setStack(undoStack); KisConfig cfg; m_undoView->stack()->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo()); m_undoView->stack()->setTimeT1(cfg.stackT1()); m_undoView->stack()->setTimeT2(cfg.stackT2()); m_undoView->stack()->setStrokesN(cfg.stackN()); } m_undoView->setCanvas( myCanvas ); } +void HistoryDock::configure() +{ + DlgConfigureHistoryDock dlg(m_undoView->stack(), this); + dlg.exec(); +} diff --git a/plugins/dockers/historydocker/HistoryDock.h b/plugins/dockers/historydocker/HistoryDock.h index 331c63bb41..8e531b7b5a 100644 --- a/plugins/dockers/historydocker/HistoryDock.h +++ b/plugins/dockers/historydocker/HistoryDock.h @@ -1,51 +1,57 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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 _HISTORY_DOCK_H_ #define _HISTORY_DOCK_H_ #include +#include + #include "KisUndoView.h" #include #include #include #include #include #include "kis_types.h" #include "kis_canvas2.h" #include "KisViewManager.h" #include "kis_image.h" #include "kis_paint_device.h" class HistoryDock : public QDockWidget, public KoCanvasObserverBase { Q_OBJECT public: HistoryDock(); QString observerName() { return "HistoryDock"; } virtual void setCanvas(KoCanvasBase *canvas); virtual void unsetCanvas() { m_historyCanvas = 0; setEnabled(false); m_undoView->setStack(0); } + +private Q_SLOTS: + void configure(); private: - KisUndoView* m_undoView; - KoCanvasBase* m_historyCanvas; + KisUndoView *m_undoView; + QToolButton *m_bnConfigure; + KoCanvasBase *m_historyCanvas; }; #endif diff --git a/plugins/dockers/historydocker/KisUndoView.cpp b/plugins/dockers/historydocker/KisUndoView.cpp index defbf500e8..7dc6db8a6e 100644 --- a/plugins/dockers/historydocker/KisUndoView.cpp +++ b/plugins/dockers/historydocker/KisUndoView.cpp @@ -1,392 +1,334 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "KisUndoView.h" #include "KisUndoModel.h" #ifndef QT_NO_UNDOVIEW #include #include #include #include #include #include #include #include #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" /*! \class KisUndoView \brief The KisUndoView class displays the contents of a KUndo2QStack. \since 4.2 \ingroup advanced KisUndoView is a QListView which displays the list of commands pushed on an undo stack. The most recently executed command is always selected. Selecting a different command results in a call to KUndo2QStack::setIndex(), rolling the state of the document backwards or forward to the new command. The stack can be set explicitly with setStack(). Alternatively, a KUndo2Group object can be set with setGroup(). The view will then update itself automatically whenever the active stack of the group changes. \image KisUndoView.png */ class KisUndoViewPrivate { public: KisUndoViewPrivate() : #ifndef QT_NO_UNDOGROUP group(0), #endif model(0) {} #ifndef QT_NO_UNDOGROUP QPointer group; #endif KisUndoModel *model; KisUndoView* q; void init(KisUndoView* view); }; void KisUndoViewPrivate::init(KisUndoView* view) { q = view; model = new KisUndoModel(q); q->setModel(model); q->setSelectionModel(model->selectionModel()); } /*! Constructs a new view with parent \a parent. */ KisUndoView::KisUndoView(QWidget *parent) : QListView(parent) , d(new KisUndoViewPrivate) { d->init(this); } /*! Constructs a new view with parent \a parent and sets the observed stack to \a stack. */ KisUndoView::KisUndoView(KUndo2QStack *stack, QWidget *parent) : QListView(parent) , d(new KisUndoViewPrivate) { d->init(this); setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Constructs a new view with parent \a parent and sets the observed group to \a group. The view will update itself automatically whenever the active stack of the group changes. */ KisUndoView::KisUndoView(KUndo2Group *group, QWidget *parent) : QListView(parent) , d(new KisUndoViewPrivate) { d->init(this); setGroup(group); } #endif // QT_NO_UNDOGROUP /*! Destroys this view. */ KisUndoView::~KisUndoView() { delete d; } /*! Returns the stack currently displayed by this view. If the view is looking at a KUndo2Group, this the group's active stack. \sa setStack() setGroup() */ KUndo2QStack *KisUndoView::stack() const { return d->model->stack(); } /*! Sets the stack displayed by this view to \a stack. If \a stack is 0, the view will be empty. If the view was previously looking at a KUndo2Group, the group is set to 0. \sa stack() setGroup() */ void KisUndoView::setStack(KUndo2QStack *stack) { #ifndef QT_NO_UNDOGROUP setGroup(0); #endif d->model->setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Sets the group displayed by this view to \a group. If \a group is 0, the view will be empty. The view will update itself autmiatically whenever the active stack of the group changes. \sa group() setStack() */ void KisUndoView::setGroup(KUndo2Group *group) { if (d->group == group) return; if (d->group != 0) { disconnect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); } d->group = group; if (d->group != 0) { connect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); d->model->setStack(d->group->activeStack()); } else { d->model->setStack(0); } } /*! Returns the group displayed by this view. If the view is not looking at group, this function returns 0. \sa setGroup() setStack() */ KUndo2Group *KisUndoView::group() const { return d->group; } #endif // QT_NO_UNDOGROUP /*! \property KisUndoView::emptyLabel \brief the label used for the empty state. The empty label is the topmost element in the list of commands, which represents the state of the document before any commands were pushed on the stack. The default is the string "". */ void KisUndoView::setEmptyLabel(const QString &label) { d->model->setEmptyLabel(label); } QString KisUndoView::emptyLabel() const { return d->model->emptyLabel(); } /*! \property KisUndoView::cleanIcon \brief the icon used to represent the clean state. A stack may have a clean state set with KUndo2QStack::setClean(). This is usually the state of the document at the point it was saved. KisUndoView can display an icon in the list of commands to show the clean state. If this property is a null icon, no icon is shown. The default value is the null icon. */ void KisUndoView::setCleanIcon(const QIcon &icon) { d->model->setCleanIcon(icon); } QIcon KisUndoView::cleanIcon() const { return d->model->cleanIcon(); } void KisUndoView::setCanvas(KisCanvas2 *canvas) { d->model->setCanvas(canvas); } -void KisUndoView::mousePressEvent(QMouseEvent *event) -{ - if (event->button() == Qt::RightButton) - { - QMenu menu(this); - QAction* action1 = menu.addAction(KisIconUtils::loadIcon("link"),stack()->useCumulativeUndoRedo()?i18n("Disable Cumulative Undo"):i18n("Enable Cumulative Undo")); - connect(action1, SIGNAL(triggered()), this, SLOT(toggleCumulativeUndoRedo())); - QLabel *l = new QLabel(i18n("Start merging time")); - QDoubleSpinBox *s = new KisDoubleParseSpinBox(); - s->setToolTip(i18nc("@info:tooltip", "The amount of time after a merged stroke before merging again")); - s->setRange(3,10); - s->setValue(stack()->timeT1()); - QGridLayout *g = new QGridLayout(); - g->addWidget(l); - g->addWidget(s); - QWidget *w = new QWidget(); - w->setLayout(g); - w->setVisible(stack()->useCumulativeUndoRedo()); - QWidgetAction* action2 = new QWidgetAction(s); - action2->setDefaultWidget(w); - connect(s,SIGNAL(valueChanged(double)),SLOT(setStackT1(double))); - - QLabel *l1 = new QLabel(i18n("Group time")); - QDoubleSpinBox *s1 = new KisDoubleParseSpinBox(); - s1->setToolTip(i18nc("@info:tooltip", "The amount of time every stroke should be apart from its previous stroke to be classified in one group")); - s1->setRange(0.3,s->value()); - s1->setValue(stack()->timeT2()); - QGridLayout *g1 = new QGridLayout(); - g1->addWidget(l1); - g1->addWidget(s1); - QWidget *w1 = new QWidget(); - w1->setLayout(g1); - w1->setVisible(stack()->useCumulativeUndoRedo()); - QWidgetAction* action3 = new QWidgetAction(s1); - action3->setDefaultWidget(w1); - connect(s1,SIGNAL(valueChanged(double)),SLOT(setStackT2(double))); - - QLabel *l2 = new QLabel(i18n("Split Strokes")); - QSpinBox *s2 = new KisIntParseSpinBox(); - s2->setToolTip(i18nc("@info:tooltip", "The number of last strokes which Krita should store separately")); - s2->setRange(1,stack()->undoLimit()); - s2->setValue(stack()->strokesN()); - QGridLayout *g2 = new QGridLayout(); - g1->addWidget(l2); - g1->addWidget(s2); - QWidget *w2 = new QWidget(); - w2->setLayout(g2); - w2->setVisible(stack()->useCumulativeUndoRedo()); - QWidgetAction* action4 = new QWidgetAction(s2); - action4->setDefaultWidget(w2); - connect(s2,SIGNAL(valueChanged(int)),SLOT(setStackN(int))); - - menu.addAction(action2); - menu.addAction(action3); - menu.addAction(action4); - - menu.exec(event->globalPos()); - } - else{ - QListView::mousePressEvent(event); - } -} + void KisUndoView::toggleCumulativeUndoRedo() { stack()->setUseCumulativeUndoRedo(!stack()->useCumulativeUndoRedo() ); KisConfig cfg; cfg.setCumulativeUndoRedo(stack()->useCumulativeUndoRedo()); } + void KisUndoView::setStackT1(double value) { stack()->setTimeT1(value); KisConfig cfg; cfg.setStackT1(value); } + void KisUndoView::setStackT2(double value) { stack()->setTimeT2(value); KisConfig cfg; cfg.setStackT2(value); } + void KisUndoView::setStackN(int value) { stack()->setStrokesN(value); KisConfig cfg; cfg.setStackN(value); } #endif // QT_NO_UNDOVIEW diff --git a/plugins/dockers/historydocker/KisUndoView.h b/plugins/dockers/historydocker/KisUndoView.h index de658a90cc..f21d78065e 100644 --- a/plugins/dockers/historydocker/KisUndoView.h +++ b/plugins/dockers/historydocker/KisUndoView.h @@ -1,124 +1,121 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KisUndoView_H #define KisUndoView_H #include #include #include #include #include #include #include #include "kis_canvas2.h" #ifndef QT_NO_UNDOVIEW class KisUndoViewPrivate; class KUndo2QStack; class KUndo2Group; class QIcon; class KisUndoView : public QListView { Q_OBJECT Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel) Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon) public: explicit KisUndoView(QWidget *parent = 0); explicit KisUndoView(KUndo2QStack *stack, QWidget *parent = 0); #ifndef QT_NO_UNDOGROUP explicit KisUndoView(KUndo2Group *group, QWidget *parent = 0); #endif ~KisUndoView(); KUndo2QStack *stack() const; #ifndef QT_NO_UNDOGROUP KUndo2Group *group() const; #endif void setEmptyLabel(const QString &label); QString emptyLabel() const; void setCleanIcon(const QIcon &icon); QIcon cleanIcon() const; - - void mousePressEvent(QMouseEvent *event); - //my new imba function void setCanvas(KisCanvas2* canvas); public Q_SLOTS: void setStack(KUndo2QStack *stack); void toggleCumulativeUndoRedo(); void setStackT1(double value); void setStackT2(double value); void setStackN(int value); #ifndef QT_NO_UNDOGROUP void setGroup(KUndo2Group *group); #endif private: KisUndoViewPrivate* const d; Q_DISABLE_COPY(KisUndoView) }; #endif // QT_NO_UNDOVIEW #endif // KisUndoView_H diff --git a/plugins/extensions/clonesarray/dlg_clonesarray.cpp b/plugins/extensions/clonesarray/dlg_clonesarray.cpp index f2b0d7a77f..fc5c34e789 100644 --- a/plugins/extensions/clonesarray/dlg_clonesarray.cpp +++ b/plugins/extensions/clonesarray/dlg_clonesarray.cpp @@ -1,257 +1,257 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_clonesarray.h" #include #include #include #include #include #include #include #include #include #include DlgClonesArray::DlgClonesArray(KisViewManager *view, QWidget *parent) : KoDialog(parent), m_view(view), m_applicator(0), m_baseLayer(m_view->activeLayer()) { Q_ASSERT(m_baseLayer); setCaption(i18n("Create Clones Array")); setButtons(Ok | Apply | Cancel); setDefaultButton(Ok); setObjectName("clones_array_dialog"); m_page = new WdgClonesArray(this); Q_CHECK_PTR(m_page); m_page->setObjectName("clones_array"); setMainWidget(m_page); resize(m_page->sizeHint()); connect(this, SIGNAL(okClicked()), SLOT(okClicked())); connect(this, SIGNAL(applyClicked()), SLOT(applyClicked())); connect(this, SIGNAL(cancelClicked()), SLOT(cancelClicked())); connect(m_page->columnXOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->columnYOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->rowXOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->rowYOffset, SIGNAL(valueChanged(int)), SLOT(syncOrthogonalToAngular())); connect(m_page->columnDistance, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->columnAngle, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->rowDistance, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->rowAngle, SIGNAL(valueChanged(double)), SLOT(syncAngularToOrthogonal())); connect(m_page->numNegativeColumns, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numPositiveColumns, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numNegativeRows, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numPositiveRows, SIGNAL(valueChanged(int)), SLOT(setDirty())); connect(m_page->numNegativeColumns, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numPositiveColumns, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numNegativeRows, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->numPositiveRows, SIGNAL(valueChanged(int)), SLOT(updateCheckboxAvailability())); connect(m_page->columnPreference, SIGNAL(stateChanged(int)), SLOT(setDirty())); initializeValues(); updateCheckboxAvailability(); } DlgClonesArray::~DlgClonesArray() { delete m_page; } void DlgClonesArray::initializeValues() { if (m_baseLayer && m_baseLayer->original()) { QRect bounds = m_baseLayer->original()->exactBounds(); m_page->columnXOffset->setValue(bounds.width()); m_page->rowYOffset->setValue(bounds.height()); } } void DlgClonesArray::setDirty() { m_isDirty = true; enableButtonApply(m_isDirty); } void DlgClonesArray::setClean() { m_isDirty = false; enableButtonApply(m_isDirty); } void DlgClonesArray::updateCheckboxAvailability() { m_page->columnPreference->setEnabled( m_page->numNegativeColumns->value() > 0 || m_page->numNegativeRows->value() > 0); } void DlgClonesArray::syncOrthogonalToAngular() { setAngularSignalsEnabled(false); int x, y; x = m_page->columnXOffset->value(); y = m_page->columnYOffset->value(); m_page->columnDistance->setValue((float)sqrt(pow2(x) + pow2(y))); m_page->columnAngle->setValue(kisRadiansToDegrees(atan2((double) y, (double) x))); x = m_page->rowXOffset->value(); y = m_page->rowYOffset->value(); m_page->rowDistance->setValue((float)sqrt(pow2(x) + pow2(y))); m_page->rowAngle->setValue(kisRadiansToDegrees(atan2((double) y, (double) x))); setAngularSignalsEnabled(true); setDirty(); } void DlgClonesArray::syncAngularToOrthogonal() { setOrthogonalSignalsEnabled(false); qreal a, d; d = m_page->columnDistance->value(); a = kisDegreesToRadians(m_page->columnAngle->value()); m_page->columnXOffset->setValue(qRound(d * cos(a))); m_page->columnYOffset->setValue(qRound(d * sin(a))); d = m_page->rowDistance->value(); a = kisDegreesToRadians(m_page->rowAngle->value()); m_page->rowXOffset->setValue(qRound(d * cos(a))); m_page->rowYOffset->setValue(qRound(d * sin(a))); setOrthogonalSignalsEnabled(true); setDirty(); } void DlgClonesArray::setOrthogonalSignalsEnabled(bool value) { m_page->columnXOffset->blockSignals(!value); m_page->columnYOffset->blockSignals(!value); m_page->rowXOffset->blockSignals(!value); m_page->rowYOffset->blockSignals(!value); } void DlgClonesArray::setAngularSignalsEnabled(bool value) { m_page->columnDistance->blockSignals(!value); m_page->columnAngle->blockSignals(!value); m_page->rowDistance->blockSignals(!value); m_page->rowAngle->blockSignals(!value); } void DlgClonesArray::okClicked() { if (!m_applicator || m_isDirty) { reapplyClones(); } Q_ASSERT(m_applicator); m_applicator->end(); delete m_applicator; m_applicator = 0; } void DlgClonesArray::applyClicked() { reapplyClones(); } void DlgClonesArray::cancelClicked() { if (m_applicator) { m_applicator->cancel(); delete m_applicator; m_applicator = 0; } } void DlgClonesArray::reapplyClones() { cancelClicked(); - KisImageWSP image = m_view->image(); - image->barrierLock(); - image->unlock(); + KisImageSP image = m_view->image(); + + if (!m_view->blockUntillOperationsFinished(image)) return; m_applicator = new KisProcessingApplicator(image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal); int columnXOffset = m_page->columnXOffset->value(); int columnYOffset = m_page->columnYOffset->value(); int rowXOffset = m_page->rowXOffset->value(); int rowYOffset = m_page->rowYOffset->value(); bool rowPreference = !m_page->columnPreference->isChecked(); int startColumn = -m_page->numNegativeColumns->value(); int startRow = -m_page->numNegativeRows->value(); int endColumn = m_page->numPositiveColumns->value() - 1; int endRow = m_page->numPositiveRows->value() - 1; QString positiveGroupName = QString(i18n("+ Array of %1")).arg(m_baseLayer->name()); KisGroupLayerSP positiveGroupLayer = new KisGroupLayer(image, positiveGroupName, OPACITY_OPAQUE_U8); m_applicator->applyCommand(new KisImageLayerAddCommand(image, positiveGroupLayer, m_baseLayer->parent(), m_baseLayer, false, true), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); KisGroupLayerSP negativeGroupLayer; if (startRow < 0 || startColumn < 0) { QString negativeGroupName = QString(i18n("- Array of %1")).arg(m_baseLayer->name()); negativeGroupLayer = new KisGroupLayer(image, negativeGroupName, OPACITY_OPAQUE_U8); m_applicator->applyCommand(new KisImageLayerAddCommand(image, negativeGroupLayer, m_baseLayer->parent(), m_baseLayer->prevSibling(), false, true), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } for (int row = endRow; row >= startRow; row--) { for (int col = endColumn; col >= startColumn; col--) { if (!col && !row) continue; bool choosePositiveGroup = rowPreference ? row > 0 || (row == 0 && col > 0) : col > 0 || (col == 0 && row > 0); KisNodeSP parent = choosePositiveGroup ? positiveGroupLayer : negativeGroupLayer; QString cloneName = QString("Clone %1, %2").arg(col).arg(row); KisCloneLayerSP clone = new KisCloneLayer(m_baseLayer, image, cloneName, OPACITY_OPAQUE_U8); clone->setX(-row * rowXOffset + col * columnXOffset); clone->setY(-row * rowYOffset + col * columnYOffset); m_applicator->applyCommand(new KisImageLayerAddCommand(image, clone, parent, 0, true, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); } } setClean(); } diff --git a/plugins/extensions/imagesplit/dlg_imagesplit.cpp b/plugins/extensions/imagesplit/dlg_imagesplit.cpp index 81f82e2895..10fef07819 100644 --- a/plugins/extensions/imagesplit/dlg_imagesplit.cpp +++ b/plugins/extensions/imagesplit/dlg_imagesplit.cpp @@ -1,111 +1,111 @@ /* * dlg_imagesplit.cc - part of KimageShop^WKrayon^WKrita * * Copyright (c) 2009 Boudewijn Rempt * Copyright (c) 2011 Srikanth Tiyyagura * * 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 "dlg_imagesplit.h" #include #include #include #include #include -DlgImagesplit::DlgImagesplit(KisViewManager* view, QString suffix, QStringList listMimeFilter) +DlgImagesplit::DlgImagesplit(KisViewManager* view, const QString &suffix, QStringList listMimeFilter, int defaultMimeIndex) : KoDialog(view->mainWindow()) { m_page = new WdgImagesplit(this); setCaption(i18n("Image Split")); setButtons(Apply | Close); setDefaultButton(Apply); connect(this, SIGNAL(applyClicked()), this, SLOT(applyClicked())); setMainWidget(m_page); m_page->lineEdit->setText(suffix); m_page->setMinimumWidth(200); m_page->setMinimumHeight(160); resize(m_page->sizeHint()); m_page->cmbFileType->clear(); m_page->cmbFileType->addItems(listMimeFilter); - m_page->cmbFileType->setCurrentIndex(0); - cmbIndex = 0; + m_page->cmbFileType->setCurrentIndex(defaultMimeIndex); + cmbIndex = defaultMimeIndex; connect(m_page->chkAutoSave, SIGNAL(stateChanged(int)), SLOT(lineEditEnable())); connect(m_page->cmbFileType, SIGNAL(activated(int)), this, SLOT(setMimeType(int))); } DlgImagesplit::~DlgImagesplit() { } void DlgImagesplit::lineEditEnable() { if (m_page->chkAutoSave->isChecked()) { m_page->lblSuffix->setEnabled(true); m_page->lineEdit->setEnabled(true); m_page->lblFileType->setEnabled(true); m_page->cmbFileType->setEnabled(true); } else { m_page->lblSuffix->setEnabled(false); m_page->lineEdit->setEnabled(false); m_page->lblFileType->setEnabled(false); m_page->cmbFileType->setEnabled(false); } } bool DlgImagesplit::autoSave() { if (m_page->chkAutoSave->isChecked()) return true; else return false; } int DlgImagesplit::horizontalLines() { return m_page->intHorizontalSplitLines->value(); } int DlgImagesplit::verticalLines() { return m_page->intVerticalSplitLines->value(); } QString DlgImagesplit::suffix() { return m_page->lineEdit->text(); } void DlgImagesplit::setMimeType(int index) { cmbIndex = index; } void DlgImagesplit::applyClicked() { accept(); } diff --git a/plugins/extensions/imagesplit/dlg_imagesplit.h b/plugins/extensions/imagesplit/dlg_imagesplit.h index 82692857c8..7f6de88308 100644 --- a/plugins/extensions/imagesplit/dlg_imagesplit.h +++ b/plugins/extensions/imagesplit/dlg_imagesplit.h @@ -1,58 +1,58 @@ /* * dlg_imagesplit.h -- part of KimageShop^WKrayon^WKrita * * Copyright (c) 2009 Boudewijn Rempt * Copyright (c) 2011 Srikanth Tiyyagura * * 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 DLG_IMAGESPLIT #define DLG_IMAGESPLIT #include #include #include "wdg_imagesplit.h" class KisViewManager; /** * This dialog allows the user to create a selection mask based * on a (range of) colors. */ class DlgImagesplit: public KoDialog { Q_OBJECT public: - DlgImagesplit(KisViewManager* view, QString suffix, QStringList listMimeType); + DlgImagesplit(KisViewManager* view, const QString &suffix, QStringList listMimeType, int defaultMimeIndex); ~DlgImagesplit(); bool autoSave(); int horizontalLines(); int verticalLines(); int cmbIndex; QString suffix(); private Q_SLOTS: void applyClicked(); void lineEditEnable(); void setMimeType(int index); private: WdgImagesplit* m_page; }; #endif // DLG_IMAGESPLIT diff --git a/plugins/extensions/imagesplit/imagesplit.cpp b/plugins/extensions/imagesplit/imagesplit.cpp index 2984046621..8e3b2653b1 100644 --- a/plugins/extensions/imagesplit/imagesplit.cpp +++ b/plugins/extensions/imagesplit/imagesplit.cpp @@ -1,173 +1,208 @@ /* * imagesplit.cc -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Srikanth Tiyyagura * * 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 "imagesplit.h" #include #include #include +#include #include #include - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_imagesplit.h" K_PLUGIN_FACTORY_WITH_JSON(ImagesplitFactory, "kritaimagesplit.json", registerPlugin();) Imagesplit::Imagesplit(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("imagesplit"); connect(action, SIGNAL(triggered()), this, SLOT(slotImagesplit())); } Imagesplit::~Imagesplit() { } -void Imagesplit::saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url) +bool Imagesplit::saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url) { KisImageWSP image = m_view->image(); KisDocument *document = KisPart::instance()->createDocument(); KisImageWSP dst = new KisImage(document->createUndoStore(), imgSize.width(), imgSize.height(), image->colorSpace(), image->objectName()); dst->setResolution(image->xRes(), image->yRes()); document->setCurrentImage(dst); KisPaintLayer* paintLayer = new KisPaintLayer(dst, dst->nextLayerName(), 255); KisPainter gc(paintLayer->paintDevice()); gc.bitBlt(QPoint(0, 0), image->projection(), imgSize); dst->addNode(paintLayer, KisNodeSP(0)); dst->refreshGraph(); - document->setFileBatchMode(true); document->setOutputMimeType(mimeType.toLatin1()); - document->exportDocument(QUrl::fromLocalFile(url)); + document->setFileBatchMode(true); + if (!document->exportDocument(QUrl::fromLocalFile(url))) { + if (document->errorMessage().isEmpty()) { + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", document->localFilePath())); + } else { + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", document->localFilePath(), document->errorMessage())); + } + return false; + } delete document; + + return true; } void Imagesplit::slotImagesplit() { // Taking the title - url from caption function and removing file extension QStringList strList = ((m_view->document())->caption()).split('.'); QString suffix = strList.at(0); // Getting all mime types and converting them into names which are displayed at combo box QStringList listMimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); + QString defaultMime = QString::fromLatin1(m_view->document()->mimeType()); + int defaultMimeIndex = 0; + listMimeFilter.sort(); QStringList filteredMimeTypes; QStringList listFileType; + int i = 0; Q_FOREACH (const QString & mimeType, listMimeFilter) { listFileType.append(KisMimeDatabase::descriptionForMimeType(mimeType)); filteredMimeTypes.append(mimeType); + if (mimeType == defaultMime) { + defaultMimeIndex = i; + } + i++; } listMimeFilter = filteredMimeTypes; Q_ASSERT(listMimeFilter.size() == listFileType.size()); - DlgImagesplit *dlgImagesplit = new DlgImagesplit(m_view, suffix, listFileType); + DlgImagesplit *dlgImagesplit = new DlgImagesplit(m_view, suffix, listFileType, defaultMimeIndex); dlgImagesplit->setObjectName("Imagesplit"); Q_CHECK_PTR(dlgImagesplit); KisImageWSP image = m_view->image(); if (dlgImagesplit->exec() == QDialog::Accepted) { int numHorizontalLines = dlgImagesplit->horizontalLines(); int numVerticalLines = dlgImagesplit->verticalLines(); int img_width = image->width() / (numVerticalLines + 1); int img_height = image->height() / (numHorizontalLines + 1); + bool stop = false; if (dlgImagesplit->autoSave()) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::OpenDirectory, "OpenDocument"); dialog.setCaption(i18n("Save Image on Split")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); + QStringList mimeFilter = m_view->document()->importExportManager()->mimeFilter(KisImportExportManager::Export); + QString defaultMime = QString::fromLatin1(m_view->document()->mimeType()); + dialog.setMimeTypeFilters(mimeFilter, defaultMime); + QUrl directory = QUrl::fromUserInput(dialog.filename()); if (directory.isEmpty()) return; for (int i = 0, k = 1; i < (numVerticalLines + 1); i++) { for (int j = 0; j < (numHorizontalLines + 1); j++, k++) { QString mimeTypeSelected = listMimeFilter.at(dlgImagesplit->cmbIndex); QString homepath = directory.toLocalFile(); QString suffix = KisMimeDatabase::suffixesForMimeType(mimeTypeSelected).first(); qDebug() << "suffix" << suffix; if (suffix.startsWith("*.")) { suffix = suffix.remove(0, 1); } qDebug() << "\tsuffix" << suffix; if (!suffix.startsWith(".")) { suffix = suffix.prepend('.'); } qDebug() << "\tsuffix" << suffix; QString fileName = dlgImagesplit->suffix() + '_' + QString::number(k) + suffix; QString url = homepath + '/' + fileName; - saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), listMimeFilter.at(dlgImagesplit->cmbIndex), url); + if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), listMimeFilter.at(dlgImagesplit->cmbIndex), url)) { + stop = true; + break; + } + } + if (stop) { + break; } } } else { for (int i = 0; i < (numVerticalLines + 1); i++) { for (int j = 0; j < (numHorizontalLines + 1); j++) { KoFileDialog dialog(m_view->mainWindow(), KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Save Image on Split")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); - dialog.setMimeTypeFilters(listMimeFilter); + dialog.setMimeTypeFilters(listMimeFilter, defaultMime); + QUrl url = QUrl::fromUserInput(dialog.filename()); QString mimefilter = KisMimeDatabase::mimeTypeForFile(url.toLocalFile()); if (url.isEmpty()) return; - saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), mimefilter, url.toLocalFile()); + if (!saveAsImage(QRect((i * img_width), (j * img_height), img_width, img_height), mimefilter, url.toLocalFile())) { + stop = true; + break; + } + } + if (stop) { + break; } } } } delete dlgImagesplit; } #include "imagesplit.moc" diff --git a/plugins/extensions/imagesplit/imagesplit.h b/plugins/extensions/imagesplit/imagesplit.h index 68dc0ac038..2c5865f630 100644 --- a/plugins/extensions/imagesplit/imagesplit.h +++ b/plugins/extensions/imagesplit/imagesplit.h @@ -1,42 +1,42 @@ /* * imagesplit.h -- Part of Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2011 Srikanth Tiyyagura * * 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 IMAGESPLIT_H #define IMAGESPLIT_H #include #include #include class Imagesplit : public KisViewPlugin { Q_OBJECT public: Imagesplit(QObject *parent, const QVariantList &); virtual ~Imagesplit(); private Q_SLOTS: void slotImagesplit(); - void saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url); + bool saveAsImage(const QRect &imgSize, const QString &mimeType, const QString &url); }; #endif // IMAGESPLIT_H diff --git a/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp b/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp index 76be075119..b099d8fdf1 100644 --- a/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp +++ b/plugins/extensions/resourcemanager/dlg_bundle_manager.cpp @@ -1,391 +1,392 @@ /* * Copyright (c) 2014 Victor Lafon metabolic.ewilan@hotmail.fr * * 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 "dlg_bundle_manager.h" #include "ui_wdgdlgbundlemanager.h" #include "resourcemanager.h" #include "dlg_create_bundle.h" #include #include #include #include #include #include #include #include "kis_action.h" #include #define ICON_SIZE 48 DlgBundleManager::DlgBundleManager(ResourceManager *resourceManager, KisActionManager* actionMgr, QWidget *parent) : KoDialog(parent) , m_page(new QWidget()) , m_ui(new Ui::WdgDlgBundleManager) , m_currentBundle(0) , m_resourceManager(resourceManager) { setCaption(i18n("Manage Resource Bundles")); m_ui->setupUi(m_page); setMainWidget(m_page); resize(m_page->sizeHint()); setButtons(Ok | Cancel); setDefaultButton(Ok); m_ui->listActive->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->listActive->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(m_ui->listActive, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(itemSelected(QListWidgetItem*,QListWidgetItem*))); connect(m_ui->listActive, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(itemSelected(QListWidgetItem*))); m_ui->listInactive->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); m_ui->listInactive->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(m_ui->listInactive, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(itemSelected(QListWidgetItem*,QListWidgetItem*))); connect(m_ui->listInactive, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(itemSelected(QListWidgetItem*))); m_ui->bnAdd->setIcon(KisIconUtils::loadIcon("arrow-right")); connect(m_ui->bnAdd, SIGNAL(clicked()), SLOT(addSelected())); m_ui->bnRemove->setIcon(KisIconUtils::loadIcon("arrow-left")); connect(m_ui->bnRemove, SIGNAL(clicked()), SLOT(removeSelected())); m_ui->listBundleContents->setHeaderLabel(i18n("Resource")); m_ui->listBundleContents->setSelectionMode(QAbstractItemView::NoSelection); m_actionManager = actionMgr; refreshListData(); connect(m_ui->bnEditBundle, SIGNAL(clicked()), SLOT(editBundle())); connect(m_ui->bnImportBrushes, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportGradients, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportPalettes, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportPatterns, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportPresets, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportWorkspaces, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->bnImportBundles, SIGNAL(clicked()), SLOT(slotImportResource())); connect(m_ui->createBundleButton, SIGNAL(clicked()), SLOT(slotCreateBundle())); connect(m_ui->deleteBackupFilesButton, SIGNAL(clicked()), SLOT(slotDeleteBackupFiles())); connect(m_ui->openResourceFolderButton, SIGNAL(clicked()), SLOT(slotOpenResourceFolder())); } void DlgBundleManager::refreshListData() { KoResourceServer *bundleServer = KisResourceServerProvider::instance()->resourceBundleServer(); m_ui->listInactive->clear(); m_ui->listActive->clear(); Q_FOREACH (const QString &f, bundleServer->blackListedFiles()) { KisResourceBundle *bundle = new KisResourceBundle(f); bundle->load(); if (bundle->valid()) { bundle->setInstalled(false); m_blacklistedBundles[f] = bundle; } } fillListWidget(m_blacklistedBundles.values(), m_ui->listInactive); Q_FOREACH (KisResourceBundle *bundle, bundleServer->resources()) { if (bundle->valid()) { m_activeBundles[bundle->filename()] = bundle; } } fillListWidget(m_activeBundles.values(), m_ui->listActive); } void DlgBundleManager::accept() { KoResourceServer *bundleServer = KisResourceServerProvider::instance()->resourceBundleServer(); for (int i = 0; i < m_ui->listActive->count(); ++i) { QListWidgetItem *item = m_ui->listActive->item(i); QByteArray ba = item->data(Qt::UserRole).toByteArray(); KisResourceBundle *bundle = bundleServer->resourceByMD5(ba); QMessageBox bundleFeedback; bundleFeedback.setIcon(QMessageBox::Warning); QString feedback = "bundlefeedback"; if (!bundle) { // Get it from the blacklisted bundles Q_FOREACH (KisResourceBundle *b2, m_blacklistedBundles.values()) { if (b2->md5() == ba) { bundle = b2; break; } } } if (bundle) { if(!bundle->isInstalled()){ bundle->install(); //this removes the bundle from the blacklist and add it to the server without saving or putting it in front// if(!bundleServer->addResource(bundle, false, false)){ feedback = i18n("Couldn't add bundle to resource server"); bundleFeedback.setText(feedback); bundleFeedback.exec(); } if(!bundleServer->removeFromBlacklist(bundle)){ feedback = i18n("Couldn't remove bundle from blacklist"); bundleFeedback.setText(feedback); bundleFeedback.exec(); } } else { bundleServer->removeFromBlacklist(bundle); //let's asume that bundles who exist and are installed have to be removed from the blacklist, and if they were already this returns false, so that's not a problem. } } else{ QString feedback = i18n("Bundle doesn't exist!"); bundleFeedback.setText(feedback); bundleFeedback.exec(); } } for (int i = 0; i < m_ui->listInactive->count(); ++i) { QListWidgetItem *item = m_ui->listInactive->item(i); QByteArray ba = item->data(Qt::UserRole).toByteArray(); KisResourceBundle *bundle = bundleServer->resourceByMD5(ba); + if (bundle && bundle->isInstalled()) { bundle->uninstall(); bundleServer->removeResourceAndBlacklist(bundle); } } KoDialog::accept(); } void DlgBundleManager::addSelected() { Q_FOREACH (QListWidgetItem *item, m_ui->listActive->selectedItems()) { m_ui->listInactive->addItem(m_ui->listActive->takeItem(m_ui->listActive->row(item))); } } void DlgBundleManager::removeSelected() { Q_FOREACH (QListWidgetItem *item, m_ui->listInactive->selectedItems()) { m_ui->listActive->addItem(m_ui->listInactive->takeItem(m_ui->listInactive->row(item))); } } void DlgBundleManager::itemSelected(QListWidgetItem *current, QListWidgetItem *) { if (!current) { m_ui->lblName->clear(); m_ui->lblAuthor->clear(); m_ui->lblEmail->clear(); m_ui->lblLicense->clear(); m_ui->lblWebsite->clear(); m_ui->lblDescription->clear(); m_ui->lblCreated->clear(); m_ui->lblUpdated->clear(); m_ui->lblPreview->setPixmap(QPixmap::fromImage(QImage())); m_ui->listBundleContents->clear(); m_ui->bnEditBundle->setEnabled(false); m_currentBundle = 0; } else { QByteArray ba = current->data(Qt::UserRole).toByteArray(); KoResourceServer *bundleServer = KisResourceServerProvider::instance()->resourceBundleServer(); KisResourceBundle *bundle = bundleServer->resourceByMD5(ba); if (!bundle) { // Get it from the blacklisted bundles Q_FOREACH (KisResourceBundle *b2, m_blacklistedBundles.values()) { if (b2->md5() == ba) { bundle = b2; break; } } } if (bundle) { m_currentBundle = bundle; m_ui->bnEditBundle->setEnabled(true); m_ui->lblName->setText(bundle->name()); m_ui->lblAuthor->setText(bundle->getMeta("author")); m_ui->lblEmail->setText(bundle->getMeta("email")); m_ui->lblLicense->setText(bundle->getMeta("license")); m_ui->lblWebsite->setText(bundle->getMeta("website")); m_ui->lblDescription->setPlainText(bundle->getMeta("description")); m_ui->lblCreated->setText(bundle->getMeta("created")); m_ui->lblUpdated->setText(bundle->getMeta("updated")); m_ui->lblPreview->setPixmap(QPixmap::fromImage(bundle->image().scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation))); m_ui->listBundleContents->clear(); Q_FOREACH (const QString & resType, bundle->resourceTypes()) { QTreeWidgetItem *toplevel = new QTreeWidgetItem(); if (resType == "gradients") { toplevel->setText(0, i18n("Gradients")); } else if (resType == "patterns") { toplevel->setText(0, i18n("Patterns")); } else if (resType == "brushes") { toplevel->setText(0, i18n("Brushes")); } else if (resType == "palettes") { toplevel->setText(0, i18n("Palettes")); } else if (resType == "workspaces") { toplevel->setText(0, i18n("Workspaces")); } else if (resType == "paintoppresets") { toplevel->setText(0, i18n("Brush Presets")); } m_ui->listBundleContents->addTopLevelItem(toplevel); Q_FOREACH (const KoResource *res, bundle->resources(resType)) { if (res) { QTreeWidgetItem *i = new QTreeWidgetItem(); i->setIcon(0, QIcon(QPixmap::fromImage(res->image()))); i->setText(0, res->name()); toplevel->addChild(i); } } } } else { m_currentBundle = 0; } } } void DlgBundleManager::itemSelected(QListWidgetItem *current) { itemSelected(current, 0); } void DlgBundleManager::editBundle() { if (m_currentBundle) { DlgCreateBundle dlg(m_currentBundle); if (dlg.exec() != QDialog::Accepted) { return; } m_resourceManager->saveBundle(dlg); } } void DlgBundleManager::fillListWidget(QList bundles, QListWidget *w) { w->setIconSize(QSize(ICON_SIZE, ICON_SIZE)); w->setSelectionMode(QAbstractItemView::MultiSelection); Q_FOREACH (KisResourceBundle *bundle, bundles) { QPixmap pixmap(ICON_SIZE, ICON_SIZE); if (!bundle->image().isNull()) { QImage scaled = bundle->image().scaled(ICON_SIZE, ICON_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); int x = (ICON_SIZE - scaled.width()) / 2; int y = (ICON_SIZE - scaled.height()) / 2; QPainter gc(&pixmap); gc.drawImage(x, y, scaled); gc.end(); } else { pixmap.fill(Qt::gray); } QListWidgetItem *item = new QListWidgetItem(pixmap, bundle->name()); item->setData(Qt::UserRole, bundle->md5()); w->addItem(item); } } void DlgBundleManager::slotImportResource() { if (m_actionManager) { QObject *button = sender(); QString buttonName = button->objectName(); KisAction *action = 0; if (buttonName == "bnImportBundles") { action = m_actionManager->actionByName("import_bundles"); } else if (buttonName == "bnImportBrushes") { action = m_actionManager->actionByName("import_brushes"); } else if (buttonName == "bnImportGradients") { action = m_actionManager->actionByName("import_gradients"); } else if (buttonName == "bnImportPalettes") { action = m_actionManager->actionByName("import_palettes"); } else if (buttonName == "bnImportPatterns") { action = m_actionManager->actionByName("import_patterns"); } else if (buttonName == "bnImportPresets") { action = m_actionManager->actionByName("import_presets"); } else if (buttonName == "bnImportWorkspaces") { action = m_actionManager->actionByName("import_workspaces"); } else { warnUI << "Unhandled bundle manager import button " << buttonName; return; } action->trigger(); refreshListData(); } } void DlgBundleManager::slotCreateBundle() { if (m_actionManager) { KisAction *action = m_actionManager->actionByName("create_bundle"); action->trigger(); } } void DlgBundleManager::slotDeleteBackupFiles() { if (m_actionManager) { KisAction *action = m_actionManager->actionByName("edit_blacklist_cleanup"); action->trigger(); } } void DlgBundleManager::slotOpenResourceFolder() { if (m_actionManager) { KisAction *action = m_actionManager->actionByName("open_resources_directory"); action->trigger(); } } diff --git a/plugins/extensions/waveletdecompose/waveletdecompose.cpp b/plugins/extensions/waveletdecompose/waveletdecompose.cpp index bd974f07ec..fc77093107 100644 --- a/plugins/extensions/waveletdecompose/waveletdecompose.cpp +++ b/plugins/extensions/waveletdecompose/waveletdecompose.cpp @@ -1,159 +1,161 @@ /* * Copyright (C) 2016 Miroslav Talasek * * 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 "waveletdecompose.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dlg_waveletdecompose.h" #include "kis_node_manager.h" #include "kis_node_commands_adapter.h" #include "kis_undo_adapter.h" #include #include K_PLUGIN_FACTORY_WITH_JSON(WaveletDecomposeFactory, "kritawaveletdecompose.json", registerPlugin();) WaveletDecompose::WaveletDecompose(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("waveletdecompose"); connect(action, SIGNAL(triggered()), this, SLOT(slotWaveletDecompose())); } WaveletDecompose::~WaveletDecompose() { } void WaveletDecompose::slotWaveletDecompose() { DlgWaveletDecompose dlg(m_view->mainWindow(), "WaveletDecompose"); if (dlg.exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::WaitCursor); KoProgressUpdater* pu = m_view->createProgressUpdater(KoProgressUpdater::Unthreaded); pu->start(100, i18n("Wavelet Decompose")); QPointer updater = pu->startSubtask(); updater->setProgress(0); KisImageSP image = m_view->image(); if (!image) return; + if (!m_view->blockUntillOperationsFinished(image)) return; + image->barrierLock(); KisPaintDeviceSP projection = new KisPaintDevice(*(image->projection()), false, 0); if (!projection) return; const KoColorSpace *cs = projection->colorSpace(); const KoCompositeOp* op = cs->compositeOp(COMPOSITE_GRAIN_EXTRACT); int scales = dlg.scales(); QList results; const QBitArray flags(0); QRect rc = image->bounds(); KisPaintDeviceSP original = projection; //main loop for(int level = 0; level < scales; ++level){ //copy original KisPaintDeviceSP blur = new KisPaintDevice(*original, false, 0); //blur it KisWaveletKernel::applyWavelet(blur, rc, 1 << level, 1 << level, flags, 0); //do grain extract blur from original KisPainter painter(original); painter.setCompositeOp(op); painter.bitBlt(0, 0, blur, 0, 0, rc.width(), rc.height()); painter.end(); //original is new scale and blur is new original results << original; original = blur; updater->setProgress((level * 100) / scales); } //add new layers KisUndoAdapter *undo = image->undoAdapter(); undo->beginMacro(kundo2_i18n("Wavelet decompose")); KisNodeCommandsAdapter adapter(m_view); KisGroupLayerSP baseGroup = image->rootLayer(); //add layer goup KisGroupLayerSP grp = new KisGroupLayer(image, i18n("Wavelet decompose"), OPACITY_OPAQUE_U8); adapter.addNode(grp, baseGroup, baseGroup->lastChild()); baseGroup = grp; //add scales int i = 1; const KoCompositeOp* op2 = cs->compositeOp(COMPOSITE_GRAIN_MERGE); Q_FOREACH (const KisPaintDeviceSP &l, results) { KisPaintLayerSP paintLayer = new KisPaintLayer(image, QStringLiteral("Scale %1").arg(i), OPACITY_OPAQUE_U8, l); adapter.addNode(paintLayer, baseGroup, 0); adapter.setCompositeOp(paintLayer, op2); ++i; } //add residual KisPaintLayerSP paintLayer = new KisPaintLayer(image, "Residual", OPACITY_OPAQUE_U8, original); adapter.addNode(paintLayer, baseGroup, 0); undo->endMacro(); updater->setProgress(100); image->unlock(); image->setModified(); } QApplication::restoreOverrideCursor(); } #include "waveletdecompose.moc" diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index e74571800b..2484dcadce 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,426 +1,420 @@ /* This file is part of the KDE project * Copyright 2008 (C) Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document) : m_d(new Private) { m_d->doc = document; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); // Legacy! Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); visitor.setSelectedNodes(m_d->doc->activeNodes()); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc,imageElement); saveGuides(doc,imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } { KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } } if (!autosave) { KisPaintDeviceSP dev = image->projection(); - if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { - dev = new KisPaintDevice(*dev.data()); - KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; - } - KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); //QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); //e.setAttribute("ColorData", QString(colorData.toBase64())); element.appendChild(e); } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); if (guides.hasGuides()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } diff --git a/plugins/impex/ora/ora_converter.cc b/plugins/impex/ora/ora_converter.cc index 2965e819de..ab7d9ffb54 100644 --- a/plugins/impex/ora/ora_converter.cc +++ b/plugins/impex/ora/ora_converter.cc @@ -1,121 +1,116 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "ora_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "ora_load_context.h" #include "ora_save_context.h" OraConverter::OraConverter(KisDocument *doc) : m_doc(doc) , m_stop(false) { } OraConverter::~OraConverter() { } KisImageBuilder_Result OraConverter::buildImage(QIODevice *io) { KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip); if (!store) { delete store; return KisImageBuilder_RESULT_FAILURE; } OraLoadContext olc(store); KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc); orslv.loadImage(); m_image = orslv.image(); m_activeNodes = orslv.activeNodes(); delete store; return KisImageBuilder_RESULT_OK; } KisImageSP OraConverter::image() { return m_image; } vKisNodeSP OraConverter::activeNodes() { return m_activeNodes; } KisImageBuilder_Result OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) { // Open file for writing KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip); if (!store) { return KisImageBuilder_RESULT_FAILURE; } OraSaveContext osc(store); KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes); image->rootLayer()->accept(orssv); if (store->open("Thumbnails/thumbnail.png")) { QSize previewSize = image->bounds().size(); previewSize.scale(QSize(256,256), Qt::KeepAspectRatio); QImage preview = image->convertToQImage(previewSize, 0); KoStoreDevice io(store); if (io.open(QIODevice::WriteOnly)) { preview.save(&io, "PNG"); } io.close(); store->close(); } KisPaintDeviceSP dev = image->projection(); - if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { - dev = new KisPaintDevice(*dev.data()); - KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; - } KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); delete store; return KisImageBuilder_RESULT_OK; } void OraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/ora/ora_save_context.cc b/plugins/impex/ora/ora_save_context.cc index 3a624b6563..2a524610de 100644 --- a/plugins/impex/ora/ora_save_context.cc +++ b/plugins/impex/ora/ora_save_context.cc @@ -1,66 +1,59 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "ora_save_context.h" #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" OraSaveContext::OraSaveContext(KoStore* _store) : m_id(0), m_store(_store) { } QString OraSaveContext::saveDeviceData(KisPaintDeviceSP dev, KisMetaData::Store* metaData, const QRect &imageRect, const qreal xRes, const qreal yRes) { QString filename = QString("data/layer%1.png").arg(m_id++); - - if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { - dev = new KisPaintDevice(*dev.data()); - KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); - delete cmd; - } - if (KisPNGConverter::saveDeviceToStore(filename, imageRect, xRes, yRes, dev, m_store, metaData)) { return filename; } return ""; } void OraSaveContext::saveStack(const QDomDocument& doc) { if (m_store->open("stack.xml")) { KoStoreDevice io(m_store); io.write(doc.toByteArray()); io.close(); m_store->close(); } else { dbgFile << "Opening of the stack.xml file failed :"; } } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp index 0241ca7aea..4a3a6a4c0a 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp @@ -1,1324 +1,1324 @@ /** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2006-09-13 * @brief LibRaw settings widgets * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2011 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * 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, 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. * * ============================================================ */ #include "dcrawsettingswidget.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "kdcraw.h" #include "rnuminput.h" #include "rcombobox.h" #include "rexpanderbox.h" #include "libkdcraw_debug.h" #include namespace KDcrawIface { class Q_DECL_HIDDEN DcrawSettingsWidget::Private { public: Private() { autoBrightnessBox = 0; sixteenBitsImage = 0; fourColorCheckBox = 0; brightnessLabel = 0; brightnessSpinBox = 0; blackPointCheckBox = 0; blackPointSpinBox = 0; whitePointCheckBox = 0; whitePointSpinBox = 0; whiteBalanceComboBox = 0; whiteBalanceLabel = 0; customWhiteBalanceSpinBox = 0; customWhiteBalanceLabel = 0; customWhiteBalanceGreenSpinBox = 0; customWhiteBalanceGreenLabel = 0; unclipColorLabel = 0; dontStretchPixelsCheckBox = 0; RAWQualityComboBox = 0; RAWQualityLabel = 0; noiseReductionComboBox = 0; NRSpinBox1 = 0; NRSpinBox2 = 0; NRLabel1 = 0; NRLabel2 = 0; enableCACorrectionBox = 0; autoCACorrectionBox = 0; caRedMultSpinBox = 0; caBlueMultSpinBox = 0; caRedMultLabel = 0; caBlueMultLabel = 0; unclipColorComboBox = 0; reconstructLabel = 0; reconstructSpinBox = 0; outputColorSpaceLabel = 0; outputColorSpaceComboBox = 0; demosaicingSettings = 0; whiteBalanceSettings = 0; correctionsSettings = 0; colormanSettings = 0; medianFilterPassesSpinBox = 0; medianFilterPassesLabel = 0; inIccUrlEdit = 0; outIccUrlEdit = 0; inputColorSpaceLabel = 0; inputColorSpaceComboBox = 0; fixColorsHighlightsBox = 0; refineInterpolationBox = 0; noiseReductionLabel = 0; expoCorrectionBox = 0; expoCorrectionShiftSpinBox = 0; expoCorrectionHighlightSpinBox = 0; expoCorrectionShiftLabel = 0; expoCorrectionHighlightLabel = 0; } /** Convert Exposure correction shift E.V value from GUI to Linear value needs by libraw decoder. */ double shiftExpoFromEvToLinear(double ev) const { // From GUI : -2.0EV => 0.25 - // +3.0EV => 8.00 + // +3.0EV => 8.00 return (1.55*ev + 3.35); } /** Convert Exposure correction shift Linear value from liraw decoder to E.V value needs by GUI. */ double shiftExpoFromLinearToEv(double lin) const { // From GUI : 0.25 => -2.0EV - // 8.00 => +3.0EV + // 8.00 => +3.0EV return ((lin-3.35) / 1.55); } public: QWidget* demosaicingSettings; QWidget* whiteBalanceSettings; QWidget* correctionsSettings; QWidget* colormanSettings; QLabel* whiteBalanceLabel; QLabel* customWhiteBalanceLabel; QLabel* customWhiteBalanceGreenLabel; QLabel* brightnessLabel; QLabel* RAWQualityLabel; QLabel* NRLabel1; QLabel* NRLabel2; QLabel* caRedMultLabel; QLabel* caBlueMultLabel; QLabel* unclipColorLabel; QLabel* reconstructLabel; QLabel* inputColorSpaceLabel; QLabel* outputColorSpaceLabel; QLabel* medianFilterPassesLabel; QLabel* noiseReductionLabel; QLabel* expoCorrectionShiftLabel; QLabel* expoCorrectionHighlightLabel; QCheckBox* blackPointCheckBox; QCheckBox* whitePointCheckBox; QCheckBox* sixteenBitsImage; QCheckBox* autoBrightnessBox; QCheckBox* fourColorCheckBox; QCheckBox* dontStretchPixelsCheckBox; QCheckBox* enableCACorrectionBox; QCheckBox* autoCACorrectionBox; QCheckBox* fixColorsHighlightsBox; QCheckBox* refineInterpolationBox; QCheckBox* expoCorrectionBox; RFileSelector* inIccUrlEdit; RFileSelector* outIccUrlEdit; RComboBox* noiseReductionComboBox; RComboBox* whiteBalanceComboBox; RComboBox* RAWQualityComboBox; RComboBox* unclipColorComboBox; RComboBox* inputColorSpaceComboBox; RComboBox* outputColorSpaceComboBox; RIntNumInput* customWhiteBalanceSpinBox; RIntNumInput* reconstructSpinBox; RIntNumInput* blackPointSpinBox; RIntNumInput* whitePointSpinBox; RIntNumInput* NRSpinBox1; RIntNumInput* NRSpinBox2; RIntNumInput* medianFilterPassesSpinBox; RDoubleNumInput* customWhiteBalanceGreenSpinBox; RDoubleNumInput* caRedMultSpinBox; RDoubleNumInput* caBlueMultSpinBox; RDoubleNumInput* brightnessSpinBox; RDoubleNumInput* expoCorrectionShiftSpinBox; RDoubleNumInput* expoCorrectionHighlightSpinBox; }; DcrawSettingsWidget::DcrawSettingsWidget(QWidget* const parent, int advSettings) : RExpanderBox(parent), d(new Private) { setup(advSettings); } void DcrawSettingsWidget::setup(int advSettings) { setObjectName( QLatin1String("DCRawSettings Expander" )); // --------------------------------------------------------------- // DEMOSAICING Settings panel d->demosaicingSettings = new QWidget(this); QGridLayout* const demosaicingLayout = new QGridLayout(d->demosaicingSettings); int line = 0; d->sixteenBitsImage = new QCheckBox(i18nc("@option:check", "16 bits color depth"), d->demosaicingSettings); - d->sixteenBitsImage->setWhatsThis(i18nc("@info:whatsthis", "If enabled, all RAW files will " + d->sixteenBitsImage->setToolTip(i18nc("@info:whatsthis", "

If enabled, all RAW files will " "be decoded in 16-bit color depth using a linear gamma curve. To " "prevent dark picture rendering in the editor, it is recommended to " - "use Color Management in this mode." - "If disabled, all RAW files will be decoded in 8-bit color " + "use Color Management in this mode.

" + "

If disabled, all RAW files will be decoded in 8-bit color " "depth with a BT.709 gamma curve and a 99th-percentile white point. " - "This mode is faster than 16-bit decoding.")); + "This mode is faster than 16-bit decoding.

")); demosaicingLayout->addWidget(d->sixteenBitsImage, 0, 0, 1, 2); if (advSettings & SIXTEENBITS) { d->sixteenBitsImage->show(); line = 1; } else { d->sixteenBitsImage->hide(); } d->fourColorCheckBox = new QCheckBox(i18nc("@option:check", "Interpolate RGB as four colors"), d->demosaicingSettings); - d->fourColorCheckBox->setWhatsThis(i18nc("@info:whatsthis", "Interpolate RGB as four " + d->fourColorCheckBox->setToolTip(i18nc("@info:whatsthis", "<title>Interpolate RGB as four " "colors" - "The default is to assume that all green pixels are the same. " + "

The default is to assume that all green pixels are the same. " "If even-row green pixels are more sensitive to ultraviolet light " "than odd-row this difference causes a mesh pattern in the output; " - "using this option solves this problem with minimal loss of detail." - "To resume, this option blurs the image a little, but it " + "using this option solves this problem with minimal loss of detail.

" + "

To resume, this option blurs the image a little, but it " "eliminates false 2x2 mesh patterns with VNG quality method or " - "mazes with AHD quality method.")); + "mazes with AHD quality method.

")); demosaicingLayout->addWidget(d->fourColorCheckBox, line, 0, 1, line == 0 ? 2 : 3); line++; QLabel* const dcrawVersion = new QLabel(d->demosaicingSettings); dcrawVersion->setAlignment(Qt::AlignRight); dcrawVersion->setToolTip(i18nc("@info:tooltip", "Visit LibRaw project website")); dcrawVersion->setOpenExternalLinks(true); dcrawVersion->setTextFormat(Qt::RichText); dcrawVersion->setTextInteractionFlags(Qt::LinksAccessibleByMouse); dcrawVersion->setText(QString::fromLatin1("%2") .arg(QLatin1String("http://www.libraw.org")) .arg(QString::fromLatin1("libraw %1").arg(KDcraw::librawVersion()))); - + demosaicingLayout->addWidget(dcrawVersion, 0, 2, 1, 1); d->dontStretchPixelsCheckBox = new QCheckBox(i18nc("@option:check", "Do not stretch or rotate pixels"), d->demosaicingSettings); - d->dontStretchPixelsCheckBox->setWhatsThis(i18nc("@info:whatsthis", + d->dontStretchPixelsCheckBox->setToolTip(i18nc("@info:whatsthis", "Do not stretch or rotate pixels" - "For Fuji Super CCD cameras, show the image tilted 45 degrees. " + "

For Fuji Super CCD cameras, show the image tilted 45 degrees. " "For cameras with non-square pixels, do not stretch the image to " "its correct aspect ratio. In any case, this option guarantees that " - "each output pixel corresponds to one RAW pixel.")); + "each output pixel corresponds to one RAW pixel.

")); demosaicingLayout->addWidget(d->dontStretchPixelsCheckBox, line, 0, 1, 3); line++; d->RAWQualityLabel = new QLabel(i18nc("@label:listbox", "Quality:"), d->demosaicingSettings); d->RAWQualityComboBox = new RComboBox(d->demosaicingSettings); // Original dcraw demosaicing methods d->RAWQualityComboBox->insertItem(RawDecodingSettings::BILINEAR, i18nc("@item:inlistbox Quality", "Bilinear")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VNG, i18nc("@item:inlistbox Quality", "VNG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PPG, i18nc("@item:inlistbox Quality", "PPG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AHD, i18nc("@item:inlistbox Quality", "AHD")); // Extended demosaicing method from GPL2 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::DCB, i18nc("@item:inlistbox Quality", "DCB")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PL_AHD, i18nc("@item:inlistbox Quality", "AHD v2")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AFD, i18nc("@item:inlistbox Quality", "AFD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD, i18nc("@item:inlistbox Quality", "VCD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD_AHD, i18nc("@item:inlistbox Quality", "VCD & AHD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::LMMSE, i18nc("@item:inlistbox Quality", "LMMSE")); // Extended demosaicing method from GPL3 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::AMAZE, i18nc("@item:inlistbox Quality", "AMaZE")); // If Libraw do not support GPL2 pack, disable entries relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) d->RAWQualityComboBox->combo()->setItemData(i, false, Qt::UserRole-1); } // If Libraw do not support GPL3 pack, disable entries relevant. if (!KDcraw::librawUseGPL3DemosaicPack()) { d->RAWQualityComboBox->combo()->setItemData(RawDecodingSettings::AMAZE, false, Qt::UserRole-1); } d->RAWQualityComboBox->setDefaultIndex(RawDecodingSettings::BILINEAR); d->RAWQualityComboBox->setCurrentIndex(RawDecodingSettings::BILINEAR); - d->RAWQualityComboBox->setWhatsThis(i18nc("@info:whatsthis", "Quality (interpolation)" - "Select here the demosaicing method to use when decoding RAW " + d->RAWQualityComboBox->setToolTip(i18nc("@info:whatsthis", "Quality (interpolation)" + "

Select here the demosaicing method to use when decoding RAW " "images. A demosaicing algorithm is a digital image process used to " "interpolate a complete image from the partial raw data received " "from the color-filtered image sensor, internal to many digital " "cameras, in form of a matrix of colored pixels. Also known as CFA " "interpolation or color reconstruction, another common spelling is " "demosaicing. The following methods are available for demosaicing " - "RAW images:" + "RAW images:

" // Original dcraw demosaicing methods - "Bilinear: use " + "

  • Bilinear: use " "high-speed but low-quality bilinear interpolation (default - for " "slow computers). In this method, the red value of a non-red pixel " "is computed as the average of the adjacent red pixels, and similarly " - "for blue and green." + "for blue and green.
  • " - "VNG: use Variable Number " + "
  • VNG: use Variable Number " "of Gradients interpolation. This method computes gradients near " "the pixel of interest and uses the lower gradients (representing " - "smoother and more similar parts of the image) to make an estimate." + "smoother and more similar parts of the image) to make an estimate.
  • " - "PPG: use Patterned-Pixel-" + "
  • PPG: use Patterned-Pixel-" "Grouping interpolation. Pixel Grouping uses assumptions about " "natural scenery in making estimates. It has fewer color artifacts " - "on natural images than the Variable Number of Gradients method." + "on natural images than the Variable Number of Gradients method.
  • " - "AHD: use Adaptive " + "
  • AHD: use Adaptive " "Homogeneity-Directed interpolation. This method selects the " "direction of interpolation so as to maximize a homogeneity metric, " - "thus typically minimizing color artifacts." + "thus typically minimizing color artifacts.
  • " // Extended demosaicing method - "DCB: DCB interpolation from " - "linuxphoto.org project." + "
  • DCB: DCB interpolation from " + "linuxphoto.org project.
  • " - "AHD v2: modified AHD " - "interpolation using Variance of Color Differences method." + "
  • AHD v2: modified AHD " + "interpolation using Variance of Color Differences method.
  • " - "AFD: Adaptive Filtered " + "
  • AFD: Adaptive Filtered " "Demosaicing interpolation through 5 pass median filter from PerfectRaw " - "project." + "project.
  • " - "VCD: Variance of Color " - "Differences interpolation." + "
  • VCD: Variance of Color " + "Differences interpolation.
  • " - "VCD & AHD: Mixed demosaicing " - "between VCD and AHD." + "
  • VCD & AHD: Mixed demosaicing " + "between VCD and AHD.
  • " - "LMMSE: color demosaicing via " + "
  • LMMSE: color demosaicing via " "directional linear minimum mean-square error estimation interpolation " - "from PerfectRaw." + "from PerfectRaw.
  • " - "AMaZE: Aliasing Minimization " + "
  • AMaZE: Aliasing Minimization " "interpolation and Zipper Elimination to apply color aberration removal " - "from RawTherapee project." - - "Note: some methods can be unavailable if RAW decoder have been built " - "without extension packs.")); + "from RawTherapee project.

" + + "

Note: some methods can be unavailable if RAW decoder have been built " + "without extension packs.

")); demosaicingLayout->addWidget(d->RAWQualityLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->RAWQualityComboBox, line, 1, 1, 2); line++; d->medianFilterPassesSpinBox = new RIntNumInput(d->demosaicingSettings); d->medianFilterPassesSpinBox->setRange(0, 10, 1); d->medianFilterPassesSpinBox->setDefaultValue(0); d->medianFilterPassesLabel = new QLabel(i18nc("@label:slider", "Pass:"), d->whiteBalanceSettings); - d->medianFilterPassesSpinBox->setWhatsThis( i18nc("@info:whatsthis", "Pass" - "Set here the passes used by the median filter applied after " - "interpolation to Red-Green and Blue-Green channels." - "This setting is only available for specific Quality options: " + d->medianFilterPassesSpinBox->setToolTip( i18nc("@info:whatsthis", "Pass" + "

Set here the passes used by the median filter applied after " + "interpolation to Red-Green and Blue-Green channels.

" + "

This setting is only available for specific Quality options: " "Bilinear, " "VNG, PPG, " "AHD, " - "DCB, and VCD & AHD.")); + "DCB, and VCD & AHD.

")); demosaicingLayout->addWidget(d->medianFilterPassesLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->medianFilterPassesSpinBox, line, 1, 1, 2); line++; d->refineInterpolationBox = new QCheckBox(i18nc("@option:check", "Refine interpolation"), d->demosaicingSettings); - d->refineInterpolationBox->setWhatsThis(i18nc("@info:whatsthis", "Refine interpolation" - "This setting is available only for few Quality options:" - "DCB: turn on " - "the enhance interpolated colors filter." - "VCD & AHD: turn on the " + d->refineInterpolationBox->setToolTip(i18nc("@info:whatsthis", "Refine interpolation" + "

This setting is available only for few Quality options:

" + "

  • DCB: turn on " + "the enhance interpolated colors filter.
  • " + "
  • VCD & AHD: turn on the " "enhanced effective color interpolation (EECI) refine to improve " - "sharpness.")); + "sharpness.

")); demosaicingLayout->addWidget(d->refineInterpolationBox, line, 0, 1, 2); - + // If Libraw do not support GPL2 pack, disable options relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); } addItem(d->demosaicingSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Demosaicing"), QString("demosaicing"), true); // --------------------------------------------------------------- // WHITE BALANCE Settings Panel d->whiteBalanceSettings = new QWidget(this); QGridLayout* const whiteBalanceLayout = new QGridLayout(d->whiteBalanceSettings); d->whiteBalanceLabel = new QLabel(i18nc("@label:listbox", "Method:"), d->whiteBalanceSettings); d->whiteBalanceComboBox = new RComboBox(d->whiteBalanceSettings); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::NONE, i18nc("@item:inlistbox", "Default D65")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CAMERA, i18nc("@item:inlistbox", "Camera")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::AUTO, i18nc("@item:inlistbox set while balance automatically", "Automatic")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CUSTOM, i18nc("@item:inlistbox set white balance manually", "Manual")); d->whiteBalanceComboBox->setDefaultIndex(RawDecodingSettings::CAMERA); - d->whiteBalanceComboBox->setWhatsThis(i18nc("@info:whatsthis", "White Balance" - "Configure the raw white balance:" - "Default D65: " - "Use a standard daylight D65 white balance." - "Camera: Use the white " + d->whiteBalanceComboBox->setToolTip(i18nc("@info:whatsthis", "White Balance" + "

Configure the raw white balance:

" + "

  • Default D65: " + "Use a standard daylight D65 white balance.
  • " + "
  • Camera: Use the white " "balance specified by the camera. If not available, reverts to " - "default neutral white balance." - "Automatic: Calculates an " - "automatic white balance averaging the entire image." - "Manual: Set a custom " - "temperature and green level values.")); + "default neutral white balance.
  • " + "
  • Automatic: Calculates an " + "automatic white balance averaging the entire image.
  • " + "
  • Manual: Set a custom " + "temperature and green level values.

")); d->customWhiteBalanceSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->customWhiteBalanceSpinBox->setRange(2000, 12000, 10); d->customWhiteBalanceSpinBox->setDefaultValue(6500); d->customWhiteBalanceLabel = new QLabel(i18nc("@label:slider", "T(K):"), d->whiteBalanceSettings); - d->customWhiteBalanceSpinBox->setWhatsThis( i18nc("@info:whatsthis", "Temperature" - "Set here the color temperature in Kelvin.")); + d->customWhiteBalanceSpinBox->setToolTip( i18nc("@info:whatsthis", "Temperature" + "

Set here the color temperature in Kelvin.

")); d->customWhiteBalanceGreenSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->customWhiteBalanceGreenSpinBox->setDecimals(2); d->customWhiteBalanceGreenSpinBox->setRange(0.2, 2.5, 0.01); d->customWhiteBalanceGreenSpinBox->setDefaultValue(1.0); d->customWhiteBalanceGreenLabel = new QLabel(i18nc("@label:slider Green component", "Green:"), d->whiteBalanceSettings); - d->customWhiteBalanceGreenSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Set here the " - "green component to set magenta color cast removal level.")); + d->customWhiteBalanceGreenSpinBox->setToolTip(i18nc("@info:whatsthis", "

Set here the " + "green component to set magenta color cast removal level.

")); d->unclipColorLabel = new QLabel(i18nc("@label:listbox", "Highlights:"), d->whiteBalanceSettings); d->unclipColorComboBox = new RComboBox(d->whiteBalanceSettings); d->unclipColorComboBox->insertItem(0, i18nc("@item:inlistbox", "Solid white")); d->unclipColorComboBox->insertItem(1, i18nc("@item:inlistbox", "Unclip")); d->unclipColorComboBox->insertItem(2, i18nc("@item:inlistbox", "Blend")); d->unclipColorComboBox->insertItem(3, i18nc("@item:inlistbox", "Rebuild")); d->unclipColorComboBox->setDefaultIndex(0); - d->unclipColorComboBox->setWhatsThis(i18nc("@info:whatsthis", "Highlights" - "Select here the highlight clipping method:" - "Solid white: " - "clip all highlights to solid white" - "Unclip: leave highlights " - "unclipped in various shades of pink" - "Blend:Blend clipped and " - "unclipped values together for a gradual fade to white" - "Rebuild: reconstruct " - "highlights using a level value")); + d->unclipColorComboBox->setToolTip(i18nc("@info:whatsthis", "Highlights" + "

Select here the highlight clipping method:

" + "

  • Solid white: " + "clip all highlights to solid white
  • " + "
  • Unclip: leave highlights " + "unclipped in various shades of pink
  • " + "
  • Blend:Blend clipped and " + "unclipped values together for a gradual fade to white
  • " + "
  • Rebuild: reconstruct " + "highlights using a level value

")); d->reconstructLabel = new QLabel(i18nc("@label:slider Highlight reconstruct level", "Level:"), d->whiteBalanceSettings); d->reconstructSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->reconstructSpinBox->setRange(0, 6, 1); d->reconstructSpinBox->setDefaultValue(0); - d->reconstructSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Level" - "Specify the reconstruct highlight level. Low values favor " - "whites and high values favor colors.")); + d->reconstructSpinBox->setToolTip(i18nc("@info:whatsthis", "Level" + "

Specify the reconstruct highlight level. Low values favor " + "whites and high values favor colors.

")); d->expoCorrectionBox = new QCheckBox(i18nc("@option:check", "Exposure Correction (E.V)"), d->whiteBalanceSettings); - d->expoCorrectionBox->setWhatsThis(i18nc("@info:whatsthis", "Turn on the exposure " - "correction before interpolation.")); + d->expoCorrectionBox->setToolTip(i18nc("@info:whatsthis", "

Turn on the exposure " + "correction before interpolation.

")); d->expoCorrectionShiftLabel = new QLabel(i18nc("@label:slider", "Linear Shift:"), d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox->setDecimals(2); d->expoCorrectionShiftSpinBox->setRange(-2.0, 3.0, 0.01); d->expoCorrectionShiftSpinBox->setDefaultValue(0.0); - d->expoCorrectionShiftSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Shift" - "Linear Shift of exposure correction before interpolation in E.V")); + d->expoCorrectionShiftSpinBox->setToolTip(i18nc("@info:whatsthis", "Shift" + "

Linear Shift of exposure correction before interpolation in E.V

")); d->expoCorrectionHighlightLabel = new QLabel(i18nc("@label:slider", "Highlight:"), d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox->setDecimals(2); d->expoCorrectionHighlightSpinBox->setRange(0.0, 1.0, 0.01); d->expoCorrectionHighlightSpinBox->setDefaultValue(0.0); - d->expoCorrectionHighlightSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Highlight" - "Amount of highlight preservation for exposure correction " - "before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V")); + d->expoCorrectionHighlightSpinBox->setToolTip(i18nc("@info:whatsthis", "Highlight" + "

Amount of highlight preservation for exposure correction " + "before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V

")); d->fixColorsHighlightsBox = new QCheckBox(i18nc("@option:check", "Correct false colors in highlights"), d->whiteBalanceSettings); - d->fixColorsHighlightsBox->setWhatsThis(i18nc("@info:whatsthis", "If enabled, images with " + d->fixColorsHighlightsBox->setToolTip(i18nc("@info:whatsthis", "

If enabled, images with " "overblown channels are processed much more accurately, without " - "'pink clouds' (and blue highlights under tungsten lamps).")); + "'pink clouds' (and blue highlights under tungsten lamps).

")); d->autoBrightnessBox = new QCheckBox(i18nc("@option:check", "Auto Brightness"), d->whiteBalanceSettings); - d->autoBrightnessBox->setWhatsThis(i18nc("@info:whatsthis", "If disable, use a fixed white level " - "and ignore the image histogram to adjust brightness.")); + d->autoBrightnessBox->setToolTip(i18nc("@info:whatsthis", "

If disable, use a fixed white level " + "and ignore the image histogram to adjust brightness.

")); d->brightnessLabel = new QLabel(i18nc("@label:slider", "Brightness:"), d->whiteBalanceSettings); d->brightnessSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->brightnessSpinBox->setDecimals(2); d->brightnessSpinBox->setRange(0.0, 10.0, 0.01); d->brightnessSpinBox->setDefaultValue(1.0); - d->brightnessSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Brightness" - "Specify the brightness level of output image. The default " - "value is 1.0 (works in 8-bit mode only).")); + d->brightnessSpinBox->setToolTip(i18nc("@info:whatsthis", "Brightness" + "

Specify the brightness level of output image. The default " + "value is 1.0 (works in 8-bit mode only).

")); if (! (advSettings & POSTPROCESSING)) { d->brightnessLabel->hide(); d->brightnessSpinBox->hide(); } d->blackPointCheckBox = new QCheckBox(i18nc("@option:check Black point", "Black:"), d->whiteBalanceSettings); - d->blackPointCheckBox->setWhatsThis(i18nc("@info:whatsthis", "Black point" - "Use a specific black point value to decode RAW pictures. If " + d->blackPointCheckBox->setToolTip(i18nc("@info:whatsthis", "Black point" + "

Use a specific black point value to decode RAW pictures. If " "you set this option to off, the Black Point value will be " - "automatically computed.")); + "automatically computed.

")); d->blackPointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->blackPointSpinBox->setRange(0, 1000, 1); d->blackPointSpinBox->setDefaultValue(0); - d->blackPointSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Black point value" - "Specify specific black point value of the output image.")); + d->blackPointSpinBox->setToolTip(i18nc("@info:whatsthis", "Black point value" + "

Specify specific black point value of the output image.

")); d->whitePointCheckBox = new QCheckBox(i18nc("@option:check White point", "White:"), d->whiteBalanceSettings); - d->whitePointCheckBox->setWhatsThis(i18nc("@info:whatsthis", "White point" - "Use a specific white point value to decode RAW pictures. If " + d->whitePointCheckBox->setToolTip(i18nc("@info:whatsthis", "White point" + "

Use a specific white point value to decode RAW pictures. If " "you set this option to off, the White Point value will be " - "automatically computed.")); + "automatically computed.

")); d->whitePointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->whitePointSpinBox->setRange(0, 20000, 1); d->whitePointSpinBox->setDefaultValue(0); - d->whitePointSpinBox->setWhatsThis(i18nc("@info:whatsthis", "White point value" - "Specify specific white point value of the output image.")); + d->whitePointSpinBox->setToolTip(i18nc("@info:whatsthis", "White point value" + "

Specify specific white point value of the output image.

")); if (! (advSettings & BLACKWHITEPOINTS)) { d->blackPointCheckBox->hide(); d->blackPointSpinBox->hide(); d->whitePointCheckBox->hide(); d->whitePointSpinBox->hide(); } whiteBalanceLayout->addWidget(d->whiteBalanceLabel, 0, 0, 1, 1); whiteBalanceLayout->addWidget(d->whiteBalanceComboBox, 0, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceLabel, 1, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceSpinBox, 1, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenLabel, 2, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenSpinBox, 2, 1, 1, 2); whiteBalanceLayout->addWidget(d->unclipColorLabel, 3, 0, 1, 1); whiteBalanceLayout->addWidget(d->unclipColorComboBox, 3, 1, 1, 2); whiteBalanceLayout->addWidget(d->reconstructLabel, 4, 0, 1, 1); whiteBalanceLayout->addWidget(d->reconstructSpinBox, 4, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionBox, 5, 0, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionShiftLabel, 6, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionShiftSpinBox, 6, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightLabel, 7, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightSpinBox, 7, 1, 1, 2); whiteBalanceLayout->addWidget(d->fixColorsHighlightsBox, 8, 0, 1, 3); whiteBalanceLayout->addWidget(d->autoBrightnessBox, 9, 0, 1, 3); whiteBalanceLayout->addWidget(d->brightnessLabel, 10, 0, 1, 1); whiteBalanceLayout->addWidget(d->brightnessSpinBox, 10, 1, 1, 2); whiteBalanceLayout->addWidget(d->blackPointCheckBox, 11, 0, 1, 1); whiteBalanceLayout->addWidget(d->blackPointSpinBox, 11, 1, 1, 2); whiteBalanceLayout->addWidget(d->whitePointCheckBox, 12, 0, 1, 1); whiteBalanceLayout->addWidget(d->whitePointSpinBox, 12, 1, 1, 2); whiteBalanceLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); whiteBalanceLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->whiteBalanceSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "White Balance"), QString("whitebalance"), true); // --------------------------------------------------------------- // CORRECTIONS Settings panel d->correctionsSettings = new QWidget(this); QGridLayout* const correctionsLayout = new QGridLayout(d->correctionsSettings); d->noiseReductionLabel = new QLabel(i18nc("@label:listbox", "Noise reduction:"), d->correctionsSettings); d->noiseReductionComboBox = new RComboBox(d->colormanSettings); d->noiseReductionComboBox->insertItem(RawDecodingSettings::NONR, i18nc("@item:inlistbox Noise Reduction", "None")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::WAVELETSNR, i18nc("@item:inlistbox Noise Reduction", "Wavelets")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::FBDDNR, i18nc("@item:inlistbox Noise Reduction", "FBDD")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::LINENR, i18nc("@item:inlistbox Noise Reduction", "CFA Line Denoise")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::IMPULSENR, i18nc("@item:inlistbox Noise Reduction", "Impulse Denoise")); d->noiseReductionComboBox->setDefaultIndex(RawDecodingSettings::NONR); - d->noiseReductionComboBox->setWhatsThis(i18nc("@info:whatsthis", "Noise Reduction" - "Select here the noise reduction method to apply during RAW " - "decoding." - "None: no " - "noise reduction." - "Wavelets: wavelets " + d->noiseReductionComboBox->setToolTip(i18nc("@info:whatsthis", "Noise Reduction" + "

Select here the noise reduction method to apply during RAW " + "decoding.

" + "

  • None: no " + "noise reduction.
  • " + "
  • Wavelets: wavelets " "correction to erase noise while preserving real detail. It's " - "applied after interpolation." - "FBDD: Fake Before " + "applied after interpolation.
  • " + "
  • FBDD: Fake Before " "Demosaicing Denoising noise reduction. It's applied before " - "interpolation." - "CFA Line Denoise: Banding " - "noise suppression. It's applied after interpolation." - "Impulse Denoise: Impulse " - "noise suppression. It's applied after interpolation.")); + "interpolation.
  • " + "
  • CFA Line Denoise: Banding " + "noise suppression. It's applied after interpolation.
  • " + "
  • Impulse Denoise: Impulse " + "noise suppression. It's applied after interpolation.

")); d->NRSpinBox1 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox1->setRange(100, 1000, 1); d->NRSpinBox1->setDefaultValue(100); d->NRLabel1 = new QLabel(d->correctionsSettings); d->NRSpinBox2 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox2->setRange(100, 1000, 1); d->NRSpinBox2->setDefaultValue(100); d->NRLabel2 = new QLabel(d->correctionsSettings); d->enableCACorrectionBox = new QCheckBox(i18nc("@option:check", "Enable Chromatic Aberration correction"), d->correctionsSettings); - d->enableCACorrectionBox->setWhatsThis(i18nc("@info:whatsthis", "Enable Chromatic " + d->enableCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "<title>Enable Chromatic " "Aberration correction" - "Enlarge the raw red-green and blue-yellow axis by the given " - "factors (automatic by default).")); + "

Enlarge the raw red-green and blue-yellow axis by the given " + "factors (automatic by default).

")); d->autoCACorrectionBox = new QCheckBox(i18nc("@option:check", "Automatic color axis adjustments"), d->correctionsSettings); - d->autoCACorrectionBox->setWhatsThis(i18nc("@info:whatsthis", "Automatic Chromatic " + d->autoCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "<title>Automatic Chromatic " "Aberration correction" - "If this option is turned on, it will try to shift image " + "

If this option is turned on, it will try to shift image " "channels slightly and evaluate Chromatic Aberration change. Note " "that if you shot blue-red pattern, the method may fail. In this " - "case, disable this option and tune manually color factors.")); + "case, disable this option and tune manually color factors.

")); d->caRedMultLabel = new QLabel(i18nc("@label:slider", "Red-Green:"), d->correctionsSettings); d->caRedMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caRedMultSpinBox->setDecimals(1); d->caRedMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caRedMultSpinBox->setDefaultValue(0.0); - d->caRedMultSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Red-Green multiplier" - "Set here the amount of correction on red-green axis")); + d->caRedMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Red-Green multiplier" + "

Set here the amount of correction on red-green axis

")); d->caBlueMultLabel = new QLabel(i18nc("@label:slider", "Blue-Yellow:"), d->correctionsSettings); d->caBlueMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caBlueMultSpinBox->setDecimals(1); d->caBlueMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caBlueMultSpinBox->setDefaultValue(0.0); - d->caBlueMultSpinBox->setWhatsThis(i18nc("@info:whatsthis", "Blue-Yellow multiplier" - "Set here the amount of correction on blue-yellow axis")); + d->caBlueMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Blue-Yellow multiplier" + "

Set here the amount of correction on blue-yellow axis

")); correctionsLayout->addWidget(d->noiseReductionLabel, 0, 0, 1, 1); correctionsLayout->addWidget(d->noiseReductionComboBox, 0, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel1, 1, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox1, 1, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel2, 2, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox2, 2, 1, 1, 2); correctionsLayout->addWidget(d->enableCACorrectionBox, 3, 0, 1, 3); correctionsLayout->addWidget(d->autoCACorrectionBox, 4, 0, 1, 3); correctionsLayout->addWidget(d->caRedMultLabel, 5, 0, 1, 1); correctionsLayout->addWidget(d->caRedMultSpinBox, 5, 1, 1, 2); correctionsLayout->addWidget(d->caBlueMultLabel, 6, 0, 1, 1); correctionsLayout->addWidget(d->caBlueMultSpinBox, 6, 1, 1, 2); correctionsLayout->setRowStretch(7, 10); correctionsLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); correctionsLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->correctionsSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Corrections"), QString("corrections"), false); // --------------------------------------------------------------- // COLOR MANAGEMENT Settings panel d->colormanSettings = new QWidget(this); QGridLayout* const colormanLayout = new QGridLayout(d->colormanSettings); d->inputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Camera Profile:"), d->colormanSettings); d->inputColorSpaceComboBox = new RComboBox(d->colormanSettings); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::NOINPUTCS, i18nc("@item:inlistbox Camera Profile", "None")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::EMBEDDED, i18nc("@item:inlistbox Camera Profile", "Embedded")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMINPUTCS, i18nc("@item:inlistbox Camera Profile", "Custom")); d->inputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::NOINPUTCS); - d->inputColorSpaceComboBox->setWhatsThis(i18nc("@info:whatsthis", "Camera Profile" - "Select here the input color space used to decode RAW data." - "None: no " - "input color profile is used during RAW decoding." - "Embedded: use embedded " - "color profile from RAW file, if it exists." - "Custom: use a custom " - "input color space profile.")); + d->inputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Camera Profile" + "

Select here the input color space used to decode RAW data.

" + "

  • None: no " + "input color profile is used during RAW decoding.
  • " + "
  • Embedded: use embedded " + "color profile from RAW file, if it exists.
  • " + "
  • Custom: use a custom " + "input color space profile.

")); d->inIccUrlEdit = new RFileSelector(d->colormanSettings); d->inIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->inIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); d->outputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Workspace:"), d->colormanSettings); d->outputColorSpaceComboBox = new RComboBox( d->colormanSettings ); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::RAWCOLOR, i18nc("@item:inlistbox Workspace", "Raw (no profile)")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::SRGB, i18nc("@item:inlistbox Workspace", "sRGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::ADOBERGB, i18nc("@item:inlistbox Workspace", "Adobe RGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::WIDEGAMMUT, i18nc("@item:inlistbox Workspace", "Wide Gamut")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::PROPHOTO, i18nc("@item:inlistbox Workspace", "Pro-Photo")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMOUTPUTCS, i18nc("@item:inlistbox Workspace", "Custom")); d->outputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::SRGB); - d->outputColorSpaceComboBox->setWhatsThis(i18nc("@info:whatsthis", "Workspace" - "Select here the output color space used to decode RAW data." - "Raw (linear): " - "in this mode, no output color space is used during RAW decoding." - "sRGB: this is an RGB " + d->outputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Workspace" + "

Select here the output color space used to decode RAW data.

" + "

  • Raw (linear): " + "in this mode, no output color space is used during RAW decoding.
  • " + "
  • sRGB: this is an RGB " "color space, created cooperatively by Hewlett-Packard and " "Microsoft. It is the best choice for images destined for the Web " - "and portrait photography." - "Adobe RGB: this color " + "and portrait photography.
  • " + "
  • Adobe RGB: this color " "space is an extended RGB color space, developed by Adobe. It is " "used for photography applications such as advertising and fine " - "art." - "Wide Gamut: this color " - "space is an expanded version of the Adobe RGB color space." - "Pro-Photo: this color " + "art.
  • " + "
  • Wide Gamut: this color " + "space is an expanded version of the Adobe RGB color space.
  • " + "
  • Pro-Photo: this color " "space is an RGB color space, developed by Kodak, that offers an " "especially large gamut designed for use with photographic outputs " - "in mind." - "Custom: use a custom " - "output color space profile.")); + "in mind.
  • " + "
  • Custom: use a custom " + "output color space profile.

")); d->outIccUrlEdit = new RFileSelector(d->colormanSettings); d->outIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->outIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); - + colormanLayout->addWidget(d->inputColorSpaceLabel, 0, 0, 1, 1); colormanLayout->addWidget(d->inputColorSpaceComboBox, 0, 1, 1, 2); colormanLayout->addWidget(d->inIccUrlEdit, 1, 0, 1, 3); colormanLayout->addWidget(d->outputColorSpaceLabel, 2, 0, 1, 1); colormanLayout->addWidget(d->outputColorSpaceComboBox, 2, 1, 1, 2); colormanLayout->addWidget(d->outIccUrlEdit, 3, 0, 1, 3); colormanLayout->setRowStretch(4, 10); colormanLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); colormanLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->colormanSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Color Management"), QString("colormanagement"), false); if (! (advSettings & COLORSPACE)) removeItem(COLORMANAGEMENT); addStretch(); // --------------------------------------------------------------- connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotUnclipColorActivated); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotWhiteBalanceToggled); connect(d->noiseReductionComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotNoiseReductionChanged); connect(d->enableCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotCACorrectionToggled); connect(d->autoCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotAutoCAToggled); connect(d->blackPointCheckBox, SIGNAL(toggled(bool)), d->blackPointSpinBox, SLOT(setEnabled(bool))); connect(d->whitePointCheckBox, SIGNAL(toggled(bool)), d->whitePointSpinBox, SLOT(setEnabled(bool))); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotsixteenBitsImageToggled); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotInputColorSpaceChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotOutputColorSpaceChanged); connect(d->expoCorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotExposureCorrectionToggled); connect(d->expoCorrectionShiftSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::slotExpoCorrectionShiftChanged); // Wrapper to emit signal when something is changed in settings. connect(d->inIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->RAWQualityComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotRAWQualityChanged); connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fixColorsHighlightsBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->autoBrightnessBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fourColorCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->dontStretchPixelsCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->refineInterpolationBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->reconstructSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox1, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox2, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); - + connect(d->medianFilterPassesSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceGreenSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caRedMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caBlueMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->brightnessSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->expoCorrectionHighlightSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); } DcrawSettingsWidget::~DcrawSettingsWidget() { delete d; } void DcrawSettingsWidget::updateMinimumWidth() { int width = 0; for (int i = 0; i < count(); i++) { if (widget(i)->width() > width) width = widget(i)->width(); } setMinimumWidth(width); } RFileSelector* DcrawSettingsWidget::inputProfileUrlEdit() const { return d->inIccUrlEdit; } RFileSelector* DcrawSettingsWidget::outputProfileUrlEdit() const { return d->outIccUrlEdit; } void DcrawSettingsWidget::resetToDefault() { setSettings(RawDecodingSettings()); } void DcrawSettingsWidget::slotsixteenBitsImageToggled(bool b) { setEnabledBrightnessSettings(!b); emit signalSixteenBitsImageToggled(d->sixteenBitsImage->isChecked()); } void DcrawSettingsWidget::slotWhiteBalanceToggled(int v) { if (v == 3) { d->customWhiteBalanceSpinBox->setEnabled(true); d->customWhiteBalanceGreenSpinBox->setEnabled(true); d->customWhiteBalanceLabel->setEnabled(true); d->customWhiteBalanceGreenLabel->setEnabled(true); } else { d->customWhiteBalanceSpinBox->setEnabled(false); d->customWhiteBalanceGreenSpinBox->setEnabled(false); d->customWhiteBalanceLabel->setEnabled(false); d->customWhiteBalanceGreenLabel->setEnabled(false); } } void DcrawSettingsWidget::slotUnclipColorActivated(int v) { if (v == 3) // Reconstruct Highlight method { d->reconstructLabel->setEnabled(true); d->reconstructSpinBox->setEnabled(true); } else { d->reconstructLabel->setEnabled(false); d->reconstructSpinBox->setEnabled(false); } } void DcrawSettingsWidget::slotNoiseReductionChanged(int item) { d->NRSpinBox1->setEnabled(true); d->NRLabel1->setEnabled(true); d->NRSpinBox2->setEnabled(true); d->NRLabel2->setEnabled(true); d->NRLabel1->setText(i18nc("@label", "Threshold:")); - d->NRSpinBox1->setWhatsThis(i18nc("@info:whatsthis", "Threshold" - "Set here the noise reduction threshold value to use.")); + d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Threshold" + "

Set here the noise reduction threshold value to use.

")); switch(item) { case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; case RawDecodingSettings::IMPULSENR: d->NRLabel1->setText(i18nc("@label", "Luminance:")); - d->NRSpinBox1->setWhatsThis(i18nc("@info:whatsthis", "Luminance" - "Amount of Luminance impulse noise reduction.")); + d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Luminance" + "

Amount of Luminance impulse noise reduction.

")); d->NRLabel2->setText(i18nc("@label", "Chrominance:")); - d->NRSpinBox2->setWhatsThis(i18nc("@info:whatsthis", "Chrominance" - "Amount of Chrominance impulse noise reduction.")); + d->NRSpinBox2->setToolTip(i18nc("@info:whatsthis", "Chrominance" + "

Amount of Chrominance impulse noise reduction.

")); d->NRSpinBox2->setVisible(true); d->NRLabel2->setVisible(true); break; default: d->NRSpinBox1->setEnabled(false); d->NRLabel1->setEnabled(false); d->NRSpinBox2->setEnabled(false); d->NRLabel2->setEnabled(false); d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; } emit signalSettingsChanged(); } void DcrawSettingsWidget::slotCACorrectionToggled(bool b) { d->autoCACorrectionBox->setEnabled(b); slotAutoCAToggled(d->autoCACorrectionBox->isChecked()); } void DcrawSettingsWidget::slotAutoCAToggled(bool b) { if (b) { d->caRedMultSpinBox->setValue(0.0); d->caBlueMultSpinBox->setValue(0.0); } bool mult = (!b) && (d->autoCACorrectionBox->isEnabled()); d->caRedMultSpinBox->setEnabled(mult); d->caBlueMultSpinBox->setEnabled(mult); d->caRedMultLabel->setEnabled(mult); d->caBlueMultLabel->setEnabled(mult); emit signalSettingsChanged(); } void DcrawSettingsWidget::slotExposureCorrectionToggled(bool b) { d->expoCorrectionShiftLabel->setEnabled(b); d->expoCorrectionShiftSpinBox->setEnabled(b); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); slotExpoCorrectionShiftChanged(d->expoCorrectionShiftSpinBox->value()); } void DcrawSettingsWidget::slotExpoCorrectionShiftChanged(double ev) { // Only enable Highligh exposure correction if Shift correction is >= 1.0, else this settings do not take effect. bool b = (ev >= 1.0); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); - + emit signalSettingsChanged(); } void DcrawSettingsWidget::slotInputColorSpaceChanged(int item) { d->inIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMINPUTCS); } void DcrawSettingsWidget::slotOutputColorSpaceChanged(int item) { d->outIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMOUTPUTCS); } void DcrawSettingsWidget::slotRAWQualityChanged(int quality) { switch(quality) { case RawDecodingSettings::DCB: case RawDecodingSettings::VCD_AHD: // These options can be only avaialble if Libraw use GPL2 pack. d->medianFilterPassesLabel->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->medianFilterPassesSpinBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->refineInterpolationBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); break; case RawDecodingSettings::PL_AHD: case RawDecodingSettings::AFD: case RawDecodingSettings::VCD: case RawDecodingSettings::LMMSE: case RawDecodingSettings::AMAZE: d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); break; default: // BILINEAR, VNG, PPG, AHD d->medianFilterPassesLabel->setEnabled(true); d->medianFilterPassesSpinBox->setEnabled(true); d->refineInterpolationBox->setEnabled(false); break; } - + emit signalSettingsChanged(); } void DcrawSettingsWidget::setEnabledBrightnessSettings(bool b) { d->brightnessLabel->setEnabled(b); d->brightnessSpinBox->setEnabled(b); } bool DcrawSettingsWidget::brightnessSettingsIsEnabled() const { return d->brightnessSpinBox->isEnabled(); } void DcrawSettingsWidget::setSettings(const RawDecodingSettings& settings) { d->sixteenBitsImage->setChecked(settings.sixteenBitsImage); switch(settings.whiteBalance) { case RawDecodingSettings::CAMERA: d->whiteBalanceComboBox->setCurrentIndex(1); break; case RawDecodingSettings::AUTO: d->whiteBalanceComboBox->setCurrentIndex(2); break; case RawDecodingSettings::CUSTOM: d->whiteBalanceComboBox->setCurrentIndex(3); break; default: d->whiteBalanceComboBox->setCurrentIndex(0); break; } slotWhiteBalanceToggled(d->whiteBalanceComboBox->currentIndex()); d->customWhiteBalanceSpinBox->setValue(settings.customWhiteBalance); d->customWhiteBalanceGreenSpinBox->setValue(settings.customWhiteBalanceGreen); d->fourColorCheckBox->setChecked(settings.RGBInterpolate4Colors); d->autoBrightnessBox->setChecked(settings.autoBrightness); d->fixColorsHighlightsBox->setChecked(settings.fixColorsHighlights); switch(settings.unclipColors) { case 0: d->unclipColorComboBox->setCurrentIndex(0); break; case 1: d->unclipColorComboBox->setCurrentIndex(1); break; case 2: d->unclipColorComboBox->setCurrentIndex(2); break; default: // Reconstruct Highlight method d->unclipColorComboBox->setCurrentIndex(3); d->reconstructSpinBox->setValue(settings.unclipColors-3); break; } slotUnclipColorActivated(d->unclipColorComboBox->currentIndex()); d->dontStretchPixelsCheckBox->setChecked(settings.DontStretchPixels); d->brightnessSpinBox->setValue(settings.brightness); d->blackPointCheckBox->setChecked(settings.enableBlackPoint); d->blackPointSpinBox->setEnabled(settings.enableBlackPoint); d->blackPointSpinBox->setValue(settings.blackPoint); d->whitePointCheckBox->setChecked(settings.enableWhitePoint); d->whitePointSpinBox->setEnabled(settings.enableWhitePoint); d->whitePointSpinBox->setValue(settings.whitePoint); int q = settings.RAWQuality; - + // If Libraw do not support GPL2 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) { if (q == i) { q = RawDecodingSettings::BILINEAR; qCDebug(LIBKDCRAW_LOG) << "Libraw GPL2 pack not avaialble. Raw quality set to Bilinear"; break; } } } // If Libraw do not support GPL3 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL3DemosaicPack() && (q == RawDecodingSettings::AMAZE)) { q = RawDecodingSettings::BILINEAR; qCDebug(LIBKDCRAW_LOG) << "Libraw GPL3 pack not avaialble. Raw quality set to Bilinear"; } - + d->RAWQualityComboBox->setCurrentIndex(q); switch(q) { case RawDecodingSettings::DCB: d->medianFilterPassesSpinBox->setValue(settings.dcbIterations); d->refineInterpolationBox->setChecked(settings.dcbEnhanceFl); break; case RawDecodingSettings::VCD_AHD: d->medianFilterPassesSpinBox->setValue(settings.eeciRefine); d->refineInterpolationBox->setChecked(settings.eeciRefine); break; default: d->medianFilterPassesSpinBox->setValue(settings.medianFilterPasses); d->refineInterpolationBox->setChecked(false); // option not used. break; } slotRAWQualityChanged(q); d->inputColorSpaceComboBox->setCurrentIndex((int)settings.inputColorSpace); slotInputColorSpaceChanged((int)settings.inputColorSpace); d->outputColorSpaceComboBox->setCurrentIndex((int)settings.outputColorSpace); slotOutputColorSpaceChanged((int)settings.outputColorSpace); d->noiseReductionComboBox->setCurrentIndex(settings.NRType); slotNoiseReductionChanged(settings.NRType); d->NRSpinBox1->setValue(settings.NRThreshold); d->NRSpinBox2->setValue(settings.NRChroThreshold); d->enableCACorrectionBox->setChecked(settings.enableCACorrection); d->caRedMultSpinBox->setValue(settings.caMultiplier[0]); d->caBlueMultSpinBox->setValue(settings.caMultiplier[1]); d->autoCACorrectionBox->setChecked((settings.caMultiplier[0] == 0.0) && (settings.caMultiplier[1] == 0.0)); slotCACorrectionToggled(settings.enableCACorrection); d->expoCorrectionBox->setChecked(settings.expoCorrection); slotExposureCorrectionToggled(settings.expoCorrection); d->expoCorrectionShiftSpinBox->setValue(d->shiftExpoFromLinearToEv(settings.expoCorrectionShift)); d->expoCorrectionHighlightSpinBox->setValue(settings.expoCorrectionHighlight); d->inIccUrlEdit->lineEdit()->setText(settings.inputProfile); d->outIccUrlEdit->lineEdit()->setText(settings.outputProfile); } RawDecodingSettings DcrawSettingsWidget::settings() const { RawDecodingSettings prm; prm.sixteenBitsImage = d->sixteenBitsImage->isChecked(); switch(d->whiteBalanceComboBox->currentIndex()) { case 1: prm.whiteBalance = RawDecodingSettings::CAMERA; break; case 2: prm.whiteBalance = RawDecodingSettings::AUTO; break; case 3: prm.whiteBalance = RawDecodingSettings::CUSTOM; break; default: prm.whiteBalance = RawDecodingSettings::NONE; break; } prm.customWhiteBalance = d->customWhiteBalanceSpinBox->value(); prm.customWhiteBalanceGreen = d->customWhiteBalanceGreenSpinBox->value(); prm.RGBInterpolate4Colors = d->fourColorCheckBox->isChecked(); prm.autoBrightness = d->autoBrightnessBox->isChecked(); prm.fixColorsHighlights = d->fixColorsHighlightsBox->isChecked(); switch(d->unclipColorComboBox->currentIndex()) { case 0: prm.unclipColors = 0; break; case 1: prm.unclipColors = 1; break; case 2: prm.unclipColors = 2; break; default: // Reconstruct Highlight method prm.unclipColors = d->reconstructSpinBox->value()+3; break; } prm.DontStretchPixels = d->dontStretchPixelsCheckBox->isChecked(); prm.brightness = d->brightnessSpinBox->value(); prm.enableBlackPoint = d->blackPointCheckBox->isChecked(); prm.blackPoint = d->blackPointSpinBox->value(); prm.enableWhitePoint = d->whitePointCheckBox->isChecked(); prm.whitePoint = d->whitePointSpinBox->value(); prm.RAWQuality = (RawDecodingSettings::DecodingQuality)d->RAWQualityComboBox->currentIndex(); switch(prm.RAWQuality) { case RawDecodingSettings::DCB: prm.dcbIterations = d->medianFilterPassesSpinBox->value(); prm.dcbEnhanceFl = d->refineInterpolationBox->isChecked(); break; case RawDecodingSettings::VCD_AHD: prm.esMedPasses = d->medianFilterPassesSpinBox->value(); prm.eeciRefine = d->refineInterpolationBox->isChecked(); break; default: prm.medianFilterPasses = d->medianFilterPassesSpinBox->value(); break; } prm.NRType = (RawDecodingSettings::NoiseReduction)d->noiseReductionComboBox->currentIndex(); switch (prm.NRType) { case RawDecodingSettings::NONR: { prm.NRThreshold = 0; prm.NRChroThreshold = 0; break; } case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = 0; break; } default: // IMPULSENR { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = d->NRSpinBox2->value(); break; } } prm.enableCACorrection = d->enableCACorrectionBox->isChecked(); prm.caMultiplier[0] = d->caRedMultSpinBox->value(); prm.caMultiplier[1] = d->caBlueMultSpinBox->value(); prm.expoCorrection = d->expoCorrectionBox->isChecked(); prm.expoCorrectionShift = d->shiftExpoFromEvToLinear(d->expoCorrectionShiftSpinBox->value()); prm.expoCorrectionHighlight = d->expoCorrectionHighlightSpinBox->value(); prm.inputColorSpace = (RawDecodingSettings::InputColorSpace)(d->inputColorSpaceComboBox->currentIndex()); prm.outputColorSpace = (RawDecodingSettings::OutputColorSpace)(d->outputColorSpaceComboBox->currentIndex()); prm.inputProfile = d->inIccUrlEdit->lineEdit()->text(); prm.outputProfile = d->outIccUrlEdit->lineEdit()->text(); return prm; } void DcrawSettingsWidget::writeSettings(KConfigGroup& group) { RawDecodingSettings prm = settings(); prm.writeSettings(group); RExpanderBox::writeSettings(group); } void DcrawSettingsWidget::readSettings(KConfigGroup& group) { RawDecodingSettings prm; prm.readSettings(group); setSettings(prm); RExpanderBox::readSettings(group); } } // NameSpace KDcrawIface diff --git a/plugins/impex/video/video_saver.cpp b/plugins/impex/video/video_saver.cpp index a070114a82..13d2c65ff7 100644 --- a/plugins/impex/video/video_saver.cpp +++ b/plugins/impex/video/video_saver.cpp @@ -1,297 +1,303 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "video_saver.h" #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_animation_exporter.h" #include #include #include #include #include #include #include "KisPart.h" class KisFFMpegProgressWatcher : public QObject { Q_OBJECT public: KisFFMpegProgressWatcher(QFile &progressFile, int totalFrames) : m_progressFile(progressFile), m_totalFrames(totalFrames) { connect(&m_progressWatcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged())); m_progressWatcher.addPath(m_progressFile.fileName()); } private Q_SLOTS: void slotFileChanged() { int currentFrame = -1; bool isEnded = false; while(!m_progressFile.atEnd()) { QString line = QString(m_progressFile.readLine()).remove(QChar('\n')); QStringList var = line.split("="); if (var[0] == "frame") { currentFrame = var[1].toInt(); } else if (var[0] == "progress") { isEnded = var[1] == "end"; } } if (isEnded) { emit sigProgressChanged(100); emit sigProcessingFinished(); } else { emit sigProgressChanged(100 * currentFrame / m_totalFrames); } } Q_SIGNALS: void sigProgressChanged(int percent); void sigProcessingFinished(); private: QFileSystemWatcher m_progressWatcher; QFile &m_progressFile; int m_totalFrames; }; class KisFFMpegRunner { public: KisFFMpegRunner(const QString &ffmpegPath) : m_cancelled(false), m_ffmpegPath(ffmpegPath) {} public: KisImageBuilder_Result runFFMpeg(const QStringList &specialArgs, const QString &actionName, const QString &logPath, int totalFrames) { dbgFile << "runFFMpeg: specialArgs" << specialArgs << "actionName" << actionName << "logPath" << logPath << "totalFrames" << totalFrames; QTemporaryFile progressFile("KritaFFmpegProgress.XXXXXX"); progressFile.open(); m_process.setStandardOutputFile(logPath); m_process.setProcessChannelMode(QProcess::MergedChannels); QStringList args; args << "-v" << "debug" << "-nostdin" << "-progress" << progressFile.fileName() << specialArgs; qDebug() << "\t" << m_ffmpegPath << args.join(" "); m_cancelled = false; m_process.start(m_ffmpegPath, args); return waitForFFMpegProcess(actionName, progressFile, m_process, totalFrames); } void cancel() { m_cancelled = true; m_process.kill(); } private: KisImageBuilder_Result waitForFFMpegProcess(const QString &message, QFile &progressFile, QProcess &ffmpegProcess, int totalFrames) { KisFFMpegProgressWatcher watcher(progressFile, totalFrames); QProgressDialog progress(message, "", 0, 0, KisPart::instance()->currentMainwindow()); progress.setWindowModality(Qt::ApplicationModal); progress.setCancelButton(0); progress.setMinimumDuration(0); progress.setValue(0); progress.setRange(0, 100); QEventLoop loop; loop.connect(&watcher, SIGNAL(sigProcessingFinished()), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(quit())); loop.connect(&watcher, SIGNAL(sigProgressChanged(int)), &progress, SLOT(setValue(int))); loop.exec(); // wait for some errorneous case ffmpegProcess.waitForFinished(5000); KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; if (ffmpegProcess.state() != QProcess::NotRunning) { // sorry... ffmpegProcess.kill(); retval = KisImageBuilder_RESULT_FAILURE; } else if (m_cancelled) { retval = KisImageBuilder_RESULT_CANCEL; } else if (ffmpegProcess.exitCode()) { retval = KisImageBuilder_RESULT_FAILURE; } return retval; } private: QProcess m_process; bool m_cancelled; QString m_ffmpegPath; }; VideoSaver::VideoSaver(KisDocument *doc, const QString &ffmpegPath, bool batchMode) : m_image(doc->image()) , m_doc(doc) , m_batchMode(batchMode) , m_ffmpegPath(ffmpegPath) , m_runner(new KisFFMpegRunner(ffmpegPath)) { } VideoSaver::~VideoSaver() { } KisImageSP VideoSaver::image() { return m_image; } bool VideoSaver::hasFFMpeg() const { return !m_ffmpegPath.isEmpty(); } KisImageBuilder_Result VideoSaver::encode(const QString &filename, KisPropertiesConfigurationSP configuration) { - dbgFile << "ffmpeg" << m_ffmpegPath << "filename" << filename << "configuration" << configuration->toXML(); + qDebug() << "ffmpeg" << m_ffmpegPath << "filename" << filename << "configuration" << configuration->toXML(); if (m_ffmpegPath.isEmpty()) { m_ffmpegPath = configuration->getString("ffmpeg_path"); if (!QFileInfo(m_ffmpegPath).exists()) { return KisImageBuilder_RESULT_FAILURE; } } KisImageBuilder_Result result = KisImageBuilder_RESULT_OK; KisImageAnimationInterface *animation = m_image->animationInterface(); const KisTimeRange fullRange = animation->fullClipRange(); const KisTimeRange clipRange(configuration->getInt("firstframe", fullRange.start()), configuration->getInt("lastFrame"), fullRange.end()); const int frameRate = animation->framerate(); const QDir framesDir(configuration->getString("directory")); - const QString resultFile = framesDir.absolutePath() + "/" + filename; + QString resultFile; + if (QFileInfo(filename).isAbsolute()) { + resultFile = filename; + } + else { + resultFile = framesDir.absolutePath() + "/" + filename; + } const QFileInfo info(resultFile); const QString suffix = info.suffix().toLower(); const QString palettePath = framesDir.filePath("palette.png"); const QString savedFilesMask = configuration->getString("savedFilesMask"); const QStringList additionalOptionsList = configuration->getString("customUserOptions").split(' ', QString::SkipEmptyParts); if (suffix == "gif") { { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << "-vf" << "palettegen" << "-y" << palettePath; KisImageBuilder_Result result = m_runner->runFFMpeg(args, i18n("Fetching palette..."), framesDir.filePath("log_generate_palette_gif.log"), clipRange.duration() + clipRange.start()); if (result != KisImageBuilder_RESULT_OK) { return result; } } { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << "-i" << palettePath << "-lavfi" << "[0:v][1:v] paletteuse" << additionalOptionsList << "-y" << resultFile; dbgFile << "savedFilesMask" << savedFilesMask << "start" << clipRange.start() << "duration" << clipRange.duration(); KisImageBuilder_Result result = m_runner->runFFMpeg(args, i18n("Encoding frames..."), framesDir.filePath("log_encode_gif.log"), clipRange.duration() + clipRange.start()); if (result != KisImageBuilder_RESULT_OK) { return result; } } } else { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << additionalOptionsList << "-y" << resultFile; result = m_runner->runFFMpeg(args, i18n("Encoding frames..."), framesDir.filePath("log_encode.log"), clipRange.duration() + clipRange.start()); } return result; } void VideoSaver::cancel() { m_runner->cancel(); } #include "video_saver.moc" diff --git a/plugins/paintops/libpaintop/CMakeLists.txt b/plugins/paintops/libpaintop/CMakeLists.txt index 32b629bd4e..153950f752 100644 --- a/plugins/paintops/libpaintop/CMakeLists.txt +++ b/plugins/paintops/libpaintop/CMakeLists.txt @@ -1,98 +1,99 @@ set(kritalibpaintop_LIB_SRCS kis_airbrush_option.cpp kis_auto_brush_widget.cpp kis_spacing_selection_widget.cpp kis_bidirectional_mixing_option.cpp kis_bidirectional_mixing_option_widget.cpp kis_brush_based_paintop.cpp kis_brush_chooser.cpp kis_brush_option_widget.cpp kis_brush_option.cpp kis_brush_selection_widget.cpp kis_color_option.cpp kis_color_source.cpp kis_color_source_option.cpp kis_color_source_option_widget.cpp kis_curve_option.cpp kis_curve_option_widget.cpp kis_curve_option_uniform_property.cpp kis_custom_brush_widget.cpp kis_clipboard_brush_widget.cpp kis_dynamic_sensor.cc kis_dab_cache.cpp kis_filter_option.cpp kis_multi_sensors_model_p.cpp kis_multi_sensors_selector.cpp kis_paint_action_type_option.cpp kis_precision_option.cpp kis_pressure_darken_option.cpp kis_pressure_hsv_option.cpp kis_pressure_opacity_option.cpp kis_pressure_flow_option.cpp kis_pressure_mirror_option.cpp kis_pressure_scatter_option.cpp kis_pressure_scatter_option_widget.cpp kis_pressure_sharpness_option.cpp kis_pressure_sharpness_option_widget.cpp kis_pressure_mirror_option_widget.cpp kis_pressure_rotation_option.cpp kis_pressure_size_option.cpp kis_pressure_spacing_option.cpp kis_pressure_softness_option.cpp kis_pressure_mix_option.cpp kis_pressure_gradient_option.cpp kis_pressure_flow_opacity_option.cpp kis_pressure_flow_opacity_option_widget.cpp kis_pressure_spacing_option_widget.cpp kis_pressure_ratio_option.cpp kis_current_outline_fetcher.cpp kis_text_brush_chooser.cpp kis_brush_based_paintop_options_widget.cpp kis_brush_based_paintop_settings.cpp kis_compositeop_option.cpp kis_texture_option.cpp kis_pressure_texture_strength_option.cpp kis_embedded_pattern_manager.cpp sensors/kis_dynamic_sensors.cc sensors/kis_dynamic_sensor_drawing_angle.cpp sensors/kis_dynamic_sensor_distance.cc sensors/kis_dynamic_sensor_time.cc sensors/kis_dynamic_sensor_fade.cpp sensors/kis_dynamic_sensor_fuzzy.cpp ) ki18n_wrap_ui(kritalibpaintop_LIB_SRCS forms/wdgautobrush.ui forms/wdgBrushSizeOptions.ui forms/wdgcurveoption.ui forms/wdgcustombrush.ui forms/wdgclipboardbrush.ui forms/wdgtextbrush.ui forms/wdgincremental.ui forms/wdgmultisensorsselector.ui forms/wdgairbrush.ui forms/wdgfilteroption.ui forms/wdgcoloroptions.ui forms/wdgbrushchooser.ui + forms/wdgpredefinedbrushchooser.ui forms/wdgCompositeOpOption.ui forms/wdgflowopacityoption.ui sensors/SensorDistanceConfiguration.ui sensors/SensorTimeConfiguration.ui sensors/SensorFadeConfiguration.ui ) add_library(kritalibpaintop SHARED ${kritalibpaintop_LIB_SRCS} ) generate_export_header(kritalibpaintop BASE_NAME kritapaintop EXPORT_MACRO_NAME PAINTOP_EXPORT) target_link_libraries(kritalibpaintop kritaui kritalibbrush kritawidgetutils) target_link_libraries(kritalibpaintop LINK_INTERFACE_LIBRARIES kritaui kritalibbrush) set_target_properties(kritalibpaintop PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibpaintop ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(tests) diff --git a/plugins/paintops/libpaintop/forms/wdgautobrush.ui b/plugins/paintops/libpaintop/forms/wdgautobrush.ui index cb5096efa3..a9eccf67c1 100644 --- a/plugins/paintops/libpaintop/forms/wdgautobrush.ui +++ b/plugins/paintops/libpaintop/forms/wdgautobrush.ui @@ -1,378 +1,432 @@ KisWdgAutoBrush 0 0 429 279 0 0 0 0 0 0 8 110 110 110 110 0 0 Mask Type: 0 0 Shape: Qt::ClickFocus Circle Square The border of the brush will be smoothed to avoid aliasing Anti-alias Qt::Vertical QSizePolicy::Expanding 20 13 Qt::Vertical QSizePolicy::Expanding 20 17 - + 200 0 Angle: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Diameter: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Ratio: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 0 0 0 0 - 0 + 1 0 0 1024 1024 Fade 4 Horizontal: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - + Vertical: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - + + + + 0 + 0 + + + + + 80 + 0 + + + + Softness: + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 10000 + 10000 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + Randomness: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Spikes: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + 200 0 - + - + - + Density: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + Spacing: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
KoAspectButton QWidget
KoAspectButton.h
1
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisCurveWidget QWidget
widgets/kis_curve_widget.h
1
KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
diff --git a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui new file mode 100644 index 0000000000..5a3d8b86bb --- /dev/null +++ b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui @@ -0,0 +1,241 @@ + + + WdgPredefinedBrushChooser + + + + 0 + 0 + 646 + 325 + + + + + + + + + 0 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + + 0 + + + + + + 0 + 0 + + + + Import + + + + + + + + 0 + 0 + + + + Stamp + + + + + + + + 0 + 0 + + + + Clipboard + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + 0 + + + + + + 12 + false + + + + false + + + Current Brush Tip + + + 2 + + + + + + + Brush Details + + + 2 + + + + + + + 5 + + + 15 + + + 10 + + + + + Rotation: + + + + + + + Spacing: + + + + + + + + 0 + 0 + + + + + + + + Size: + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + Use Color as Mask + + + + + + + Reset Predefined Tip + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + KisDoubleSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
+ + KisSpacingSelectionWidget + QWidget +
kis_spacing_selection_widget.h
+ 1 +
+
+ + +
diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index fe8aa7d982..dc2d6d5bcc 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,372 +1,383 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_brush_registry.h" #include "kis_brush_server.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include "kis_global.h" #include "kis_gbr_brush.h" #include "kis_debug.h" #include "kis_image.h" /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; KisBrush *brush = static_cast(index.internalPointer()); QRect itemRect = option.rect; QImage thumbnail = brush->image(); if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } -KisBrushChooser::KisBrushChooser(QWidget *parent, const char *name) +KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); - m_lbSize = new QLabel(i18n("Size:"), this); - m_lbSize->setObjectName("lblSize"); - m_slSize = new KisDoubleSliderSpinBox(this); - m_slSize->setObjectName("Size"); - m_slSize->setRange(0, 1000, 2); - m_slSize->setValue(5); - m_slSize->setExponentRatio(3.0); - m_slSize->setSuffix(i18n(" px")); - m_slSize->setExponentRatio(3.0); - QObject::connect(m_slSize, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); - - - m_lbRotation = new QLabel(i18n("Rotation:"), this); - m_lbRotation->setObjectName("lblRotation"); - m_slRotation = new KisDoubleSliderSpinBox(this); - m_slRotation->setObjectName("Rotation"); - m_slRotation->setRange(0, 360, 0); - m_slRotation->setValue(0); - m_slRotation->setSuffix(QChar(Qt::Key_degree)); - QObject::connect(m_slRotation, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); - - m_lbSpacing = new QLabel(i18n("Spacing:"), this); - m_lbSpacing->setObjectName("lblSpacing"); - m_slSpacing = new KisSpacingSelectionWidget(this); - m_slSpacing->setObjectName("Spacing"); - m_slSpacing->setSpacing(true, 1.0); - connect(m_slSpacing, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); - - m_chkColorMask = new QCheckBox(i18n("Use color as mask"), this); - m_chkColorMask->setObjectName("ColorAsMask"); - QObject::connect(m_chkColorMask, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool))); - - m_lbName = new QLabel(this); - m_lbName->setObjectName("lblName"); + setupUi(this); + + brushSizeSpinBox->setRange(0, 1000, 2); + brushSizeSpinBox->setValue(5); + brushSizeSpinBox->setExponentRatio(3.0); + brushSizeSpinBox->setSuffix(i18n(" px")); + brushSizeSpinBox->setExponentRatio(3.0); + + QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); + + + brushRotationSpinBox->setRange(0, 360, 0); + brushRotationSpinBox->setValue(0); + brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); + QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); + + brushSpacingSelectionWidget->setSpacing(true, 1.0); + connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); + + + QObject::connect(useColorAsMaskCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool))); + KisBrushResourceServer* rServer = KisBrushServer::instance()->brushServer(); QSharedPointer adapter(new KisBrushResourceServerAdapter(rServer)); + + m_itemChooser = new KoResourceItemChooser(adapter, this); + m_itemChooser->showTaggingBar(true); m_itemChooser->setColumnCount(10); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0, 0); m_itemChooser->setSynced(true); + m_itemChooser->setMinimumWidth(100); + m_itemChooser->setMinimumHeight(150); + m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them + + + addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); + deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); + - connect(m_itemChooser, SIGNAL(resourceSelected(KoResource *)), this, SLOT(update(KoResource *))); - QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setObjectName("main layout"); - mainLayout->addWidget(m_lbName); - mainLayout->addWidget(m_itemChooser, 10); + connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); + connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); - QPushButton *stampButton = new QPushButton(KisIconUtils::loadIcon("list-add"), i18n("Stamp"), this); - stampButton->setObjectName("AddBrush"); + presetsLayout->addWidget(m_itemChooser); + + + connect(m_itemChooser, SIGNAL(resourceSelected(KoResource *)), this, SLOT(update(KoResource *))); + + stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." - "\n If no selection is present the whole image will be used.")); + "\n If no selection is present the whole image will be used.")); - QPushButton *clipboardButton = new QPushButton(KisIconUtils::loadIcon("edit-paste"), i18n("Clipboard"), this); - clipboardButton->setObjectName("AddFromClipboard"); + clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); - m_itemChooser->addCustomButton(stampButton, 2); - m_itemChooser->addCustomButton(clipboardButton, 3); + connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); - mainLayout->addLayout(spacingLayout, 1); - - spacingLayout->addWidget(m_lbSize, 1, 0); - spacingLayout->addWidget(m_slSize, 1, 1); - spacingLayout->addWidget(m_lbRotation, 2, 0); - spacingLayout->addWidget(m_slRotation, 2, 1); - spacingLayout->addWidget(m_lbSpacing, 3, 0); - spacingLayout->addWidget(m_slSpacing, 3, 1); - spacingLayout->setColumnStretch(1, 3); - - QPushButton *resetBrushButton = new QPushButton(i18n("Reset Predefined Tip"), this); - resetBrushButton->setObjectName("ResetBrush"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); - QHBoxLayout *resetHLayout = new QHBoxLayout(); - resetHLayout->addWidget(m_chkColorMask, 0); - resetHLayout->addWidget(resetBrushButton, 0, Qt::AlignRight); - - spacingLayout->addLayout(resetHLayout, 4, 0, 1, 2); - update(m_itemChooser->currentResource()); } -KisBrushChooser::~KisBrushChooser() +KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } -void KisBrushChooser::setBrush(KisBrushSP _brush) +void KisPredefinedBrushChooser::setBrush(KisBrushSP _brush) { m_itemChooser->setCurrentResource(_brush.data()); update(_brush.data()); } -void KisBrushChooser::slotResetBrush() +void KisPredefinedBrushChooser::slotResetBrush() { KisBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { brush->load(); brush->setScale(1.0); brush->setAngle(0.0); + slotActivatedBrush(brush); update(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotSetItemSize(qreal sizeValue) +void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KisBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { int brushWidth = brush->width(); brush->setScale(sizeValue / qreal(brushWidth)); slotActivatedBrush(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotSetItemRotation(qreal rotationValue) +void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KisBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { brush->setAngle(rotationValue / 180.0 * M_PI); slotActivatedBrush(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotSpacingChanged() +void KisPredefinedBrushChooser::slotSpacingChanged() { KisBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { - brush->setSpacing(m_slSpacing->spacing()); - brush->setAutoSpacing(m_slSpacing->autoSpacingActive(), m_slSpacing->autoSpacingCoeff()); + brush->setSpacing(brushSpacingSelectionWidget->spacing()); + brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); slotActivatedBrush(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask) +void KisPredefinedBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask) { KisGbrBrush *brush = dynamic_cast(m_itemChooser->currentResource()); if (brush) { brush->setUseColorAsMask(useColorAsMask); slotActivatedBrush(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotOpenStampBrush() +void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource *)), SLOT(slotNewPredefinedBrush(KoResource *))); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { update(m_itemChooser->currentResource()); } } -void KisBrushChooser::slotOpenClipboardBrush() +void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResource *)), SLOT(slotNewPredefinedBrush(KoResource *))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { update(m_itemChooser->currentResource()); } } -void KisBrushChooser::update(KoResource * resource) +void KisPredefinedBrushChooser::update(KoResource * resource) { KisBrush* brush = dynamic_cast(resource); if (brush) { - QString text = QString("%1 (%2 x %3)") - .arg(i18n(brush->name().toUtf8().data())) + + + brushTipNameLabel->setText(i18n(brush->name().toUtf8().data())); + + QString brushTypeString = ""; + + if (brush->brushType() == INVALID) { + brushTypeString = i18n("Invalid"); + } else if (brush->brushType() == MASK) { + brushTypeString = i18n("Mask"); + } else if (brush->brushType() == IMAGE) { + brushTypeString = i18n("GBR"); + } else if (brush->brushType() == PIPE_MASK ) { + brushTypeString = i18n("Animated Mask"); + } else if (brush->brushType() == PIPE_IMAGE ) { + brushTypeString = i18n("Animated Image"); + } + + + QString brushDetailsText = QString("%1 (%2 x %3)") + .arg(brushTypeString) .arg(brush->width()) .arg(brush->height()); - m_lbName->setText(text); + brushDetailsLabel->setText(brushDetailsText); + - m_slSpacing->setSpacing(brush->autoSpacingActive(), + brushSpacingSelectionWidget->setSpacing(brush->autoSpacingActive(), brush->autoSpacingActive() ? brush->autoSpacingCoeff() : brush->spacing()); - m_slRotation->setValue(brush->angle() * 180 / M_PI); - m_slSize->setValue(brush->width() * brush->scale()); + brushRotationSpinBox->setValue(brush->angle() * 180 / M_PI); + brushSizeSpinBox->setValue(brush->width() * brush->scale()); // useColorAsMask support is only in gimp brush so far KisGbrBrush *gimpBrush = dynamic_cast(resource); if (gimpBrush) { - m_chkColorMask->setChecked(gimpBrush->useColorAsMask()); + useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask()); } - m_chkColorMask->setEnabled(brush->hasColor() && gimpBrush); + useColorAsMaskCheckbox->setEnabled(brush->hasColor() && gimpBrush); slotActivatedBrush(brush); emit sigBrushChanged(); } } -void KisBrushChooser::slotActivatedBrush(KoResource * resource) +void KisPredefinedBrushChooser::slotActivatedBrush(KoResource * resource) { KisBrush* brush = dynamic_cast(resource); if (m_brush != brush) { if (m_brush) { m_brush->clearBrushPyramid(); } m_brush = brush; if (m_brush) { m_brush->prepareBrushPyramid(); } } } -void KisBrushChooser::slotNewPredefinedBrush(KoResource *resource) +void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResource *resource) { m_itemChooser->setCurrentResource(resource); update(resource); } -void KisBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) +void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); - m_slSize->setValue(newWidth); + brushSizeSpinBox->setValue(newWidth); } -void KisBrushChooser::setImage(KisImageWSP image) +void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } +void KisPredefinedBrushChooser::slotImportNewBrushResource() { + m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Import); +} + +void KisPredefinedBrushChooser::slotDeleteBrushResource() { + m_itemChooser->slotButtonClicked(KoResourceItemChooser::Button_Remove); +} + + + #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.h b/plugins/paintops/libpaintop/kis_brush_chooser.h index 8e5ad57c61..74bcd95f74 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.h +++ b/plugins/paintops/libpaintop/kis_brush_chooser.h @@ -1,87 +1,88 @@ /* * Copyright (c) 2004 Adrian Page * * 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_BRUSH_CHOOSER_H_ -#define KIS_BRUSH_CHOOSER_H_ +#ifndef KIS_PREDEFINED_BRUSH_CHOOSER_H_ +#define KIS_PREDEFINED_BRUSH_CHOOSER_H_ #include -#include "kritapaintop_export.h" #include +#include "kritapaintop_export.h" +#include "ui_wdgpredefinedbrushchooser.h" + + + + class KisDoubleSliderSpinBox; class QLabel; class QCheckBox; class KisDoubleSliderSpinBox; class KisSpacingSelectionWidget; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KoResourceItemChooser; class KoResource; -class PAINTOP_EXPORT KisBrushChooser : public QWidget +class PAINTOP_EXPORT KisPredefinedBrushChooser : public QWidget, Ui::WdgPredefinedBrushChooser { Q_OBJECT public: - KisBrushChooser(QWidget *parent = 0, const char *name = 0); - virtual ~KisBrushChooser(); + KisPredefinedBrushChooser(QWidget *parent = 0, const char *name = 0); + virtual ~KisPredefinedBrushChooser(); KisBrushSP brush() { return m_brush; }; void setBrush(KisBrushSP _brush); void setBrushSize(qreal xPixels, qreal yPixels); void setImage(KisImageWSP image); private Q_SLOTS: void slotResetBrush(); void slotSetItemSize(qreal); void slotSetItemRotation(qreal); void slotSpacingChanged(); void slotSetItemUseColorAsMask(bool); void slotActivatedBrush(KoResource *); void slotOpenStampBrush(); void slotOpenClipboardBrush(); + void slotImportNewBrushResource(); + void slotDeleteBrushResource(); void slotNewPredefinedBrush(KoResource *); void update(KoResource *); + + Q_SIGNALS: void sigBrushChanged(); private: - QLabel* m_lbName; - QLabel* m_lbRotation; - QLabel* m_lbSize; - QLabel* m_lbSpacing; - KisDoubleSliderSpinBox* m_slRotation; - KisDoubleSliderSpinBox* m_slSize; - KisSpacingSelectionWidget* m_slSpacing; - QCheckBox* m_chkColorMask; KisBrushSP m_brush; KoResourceItemChooser* m_itemChooser; KisImageWSP m_image; KisCustomBrushWidget* m_stampBrushWidget; KisClipboardBrushWidget* m_clipboardBrushWidget; }; -#endif // KIS_BRUSH_CHOOSER_H_ +#endif // KIS_PREDEFINED_BRUSH_CHOOSER_H_ diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp index 95e9151709..5ee4c64c20 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.cpp @@ -1,359 +1,359 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_selection_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_brush.h" #include "kis_auto_brush.h" #include "kis_imagepipe_brush.h" #include "kis_brush_chooser.h" #include "kis_auto_brush_widget.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include "kis_text_brush_chooser.h" KisBrushSelectionWidget::KisBrushSelectionWidget(QWidget * parent) : QWidget(parent), m_currentBrushWidget(0) { uiWdgBrushChooser.setupUi(this); m_buttonGroup = new QButtonGroup(this); m_buttonGroup->setExclusive(true); m_layout = new QGridLayout(uiWdgBrushChooser.settingsFrame); m_autoBrushWidget = new KisAutoBrushWidget(this, "autobrush"); connect(m_autoBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); addChooser(i18n("Auto"), m_autoBrushWidget, AUTOBRUSH, KoGroupButton::GroupLeft); - m_brushChooser = new KisBrushChooser(this); - connect(m_brushChooser, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); - addChooser(i18n("Predefined"), m_brushChooser, PREDEFINEDBRUSH, KoGroupButton::GroupCenter); + m_predefinedBrushWidget = new KisPredefinedBrushChooser(this); + connect(m_predefinedBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); + addChooser(i18n("Predefined"), m_predefinedBrushWidget, PREDEFINEDBRUSH, KoGroupButton::GroupCenter); m_textBrushWidget = new KisTextBrushChooser(this, "textbrush", i18n("Text")); connect(m_textBrushWidget, SIGNAL(sigBrushChanged()), SIGNAL(sigBrushChanged())); addChooser(i18n("Text"), m_textBrushWidget, TEXTBRUSH, KoGroupButton::GroupRight); connect(m_buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(buttonClicked(int))); Q_FOREACH (QWidget * widget, m_chooserMap.values()) { m_mininmumSize = m_mininmumSize.expandedTo(widget->sizeHint()); } setCurrentWidget(m_autoBrushWidget); uiWdgBrushChooser.sliderPrecision->setRange(1, 5); uiWdgBrushChooser.sliderPrecision->setSingleStep(1); uiWdgBrushChooser.sliderPrecision->setPageStep(1); connect(uiWdgBrushChooser.sliderPrecision, SIGNAL(valueChanged(int)), SLOT(precisionChanged(int))); connect(uiWdgBrushChooser.autoPrecisionCheckBox, SIGNAL(stateChanged(int)), SLOT(setAutoPrecisionEnabled(int))); connect(uiWdgBrushChooser.deltaValueSpinBox, SIGNAL(valueChanged(double)), SLOT(setDeltaValue(double))); connect(uiWdgBrushChooser.sizeToStartFromSpinBox, SIGNAL(valueChanged(double)), SLOT(setSizeToStartFrom(double))); uiWdgBrushChooser.sliderPrecision->setValue(4); setPrecisionEnabled(false); uiWdgBrushChooser.label->setVisible(false); uiWdgBrushChooser.label_2->setVisible(false); uiWdgBrushChooser.deltaValueSpinBox->setVisible(false); uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(false); uiWdgBrushChooser.lblPrecisionValue->setVisible(false); uiWdgBrushChooser.label ->setToolTip(i18n("Use to set the size from which the Automatic Precision Setting should begin. \nThe Precision will remain 5 before this value.")); uiWdgBrushChooser.label_2 ->setToolTip(i18n("Use to set the interval at which the Automatic Precision will change. \nThe Precision will decrease as brush size increases.")); m_presetIsValid = true; } KisBrushSelectionWidget::~KisBrushSelectionWidget() { } KisBrushSP KisBrushSelectionWidget::brush() const { KisBrushSP theBrush; switch (m_buttonGroup->checkedId()) { case AUTOBRUSH: theBrush = m_autoBrushWidget->brush(); break; case PREDEFINEDBRUSH: - theBrush = m_brushChooser->brush(); + theBrush = m_predefinedBrushWidget->brush(); break; case TEXTBRUSH: theBrush = m_textBrushWidget->brush(); break; default: ; } // Fallback to auto brush if no brush selected // Can happen if there is no predefined brush found if (!theBrush) theBrush = m_autoBrushWidget->brush(); return theBrush; } void KisBrushSelectionWidget::setAutoBrush(bool on) { m_buttonGroup->button(AUTOBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setPredefinedBrushes(bool on) { m_buttonGroup->button(PREDEFINEDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setCustomBrush(bool on) { m_buttonGroup->button(CUSTOMBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setClipboardBrush(bool on) { m_buttonGroup->button(CLIPBOARDBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setTextBrush(bool on) { m_buttonGroup->button(TEXTBRUSH)->setVisible(on); } void KisBrushSelectionWidget::setImage(KisImageWSP image) { - m_brushChooser->setImage(image); + m_predefinedBrushWidget->setImage(image); } void KisBrushSelectionWidget::setCurrentBrush(KisBrushSP brush) { if (!brush) { return; } // XXX: clever code have brush plugins know their configuration // pane, so we don't have to have this if statement and // have an extensible set of brush types if (dynamic_cast(brush.data())) { setCurrentWidget(m_autoBrushWidget); m_autoBrushWidget->setBrush(brush); } else if (dynamic_cast(brush.data())) { setCurrentWidget(m_textBrushWidget); m_textBrushWidget->setBrush(brush); } else { - setCurrentWidget(m_brushChooser); - m_brushChooser->setBrush(brush); + setCurrentWidget(m_predefinedBrushWidget); + m_predefinedBrushWidget->setBrush(brush); } } void KisBrushSelectionWidget::buttonClicked(int id) { setCurrentWidget(m_chooserMap[id]); emit sigBrushChanged(); } void KisBrushSelectionWidget::precisionChanged(int value) { QString toolTip; switch (value) { case 1: toolTip = i18n("Precision Level 1 (fastest)\n" "Subpixel precision: disabled\n" "Brush size precision: 5%\n" "\n" "Optimal for very big brushes"); break; case 2: toolTip = i18n("Precision Level 2\n" "Subpixel precision: disabled\n" "Brush size precision: 1%\n" "\n" "Optimal for big brushes"); break; case 3: toolTip = i18n("Precision Level 3\n" "Subpixel precision: disabled\n" "Brush size precision: exact"); break; case 4: toolTip = i18n("Precision Level 4 (optimal)\n" "Subpixel precision: 50%\n" "Brush size precision: exact\n" "\n" "Gives up to 50% better performance in comparison to Level 5"); break; case 5: toolTip = i18n("Precision Level 5 (best quality)\n" "Subpixel precision: exact\n" "Brush size precision: exact\n" "\n" "The slowest performance. Best quality."); break; } uiWdgBrushChooser.sliderPrecision->blockSignals(true); uiWdgBrushChooser.sliderPrecision->setValue(value); uiWdgBrushChooser.sliderPrecision->blockSignals(false); uiWdgBrushChooser.sliderPrecision->setToolTip(toolTip); m_precisionOption.setPrecisionLevel(value); emit sigPrecisionChanged(); } void KisBrushSelectionWidget::writeOptionSetting(KisPropertiesConfigurationSP settings) const { m_precisionOption.writeOptionSetting(settings); } void KisBrushSelectionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { m_precisionOption.readOptionSetting(setting); uiWdgBrushChooser.sliderPrecision->setValue(m_precisionOption.precisionLevel()); uiWdgBrushChooser.autoPrecisionCheckBox->setChecked(m_precisionOption.autoPrecisionEnabled()); uiWdgBrushChooser.deltaValueSpinBox ->setValue(m_precisionOption.deltaValue()); uiWdgBrushChooser.sizeToStartFromSpinBox ->setValue(m_precisionOption.sizeToStartFrom()); } void KisBrushSelectionWidget::setPrecisionEnabled(bool value) { uiWdgBrushChooser.sliderPrecision->setVisible(value); uiWdgBrushChooser.lblPrecision->setVisible(value); } void KisBrushSelectionWidget::hideOptions(const QStringList &options) { Q_FOREACH(const QString &option, options) { QStringList l = option.split("/"); if (l.count() != 2) { continue; } QObject *o = 0; if (l[0] == "KisAutoBrushWidget") { o = m_autoBrushWidget->findChild(l[1]); } else if (l[0] == "KisBrushChooser") { - o = m_brushChooser->findChild(l[1]); + o = m_predefinedBrushWidget->findChild(l[1]); } else if (l[0] == "KisTextBrushChooser") { o = m_textBrushWidget->findChild(l[1]); } else { qWarning() << "KisBrushSelectionWidget: Invalid option given to disable:" << option; } if (o) { QWidget *w = qobject_cast(o); if (w) { w->setVisible(false); } o = 0; } } } void KisBrushSelectionWidget::setCurrentWidget(QWidget* widget) { if (widget == m_currentBrushWidget) return; if (m_currentBrushWidget) { m_layout->removeWidget(m_currentBrushWidget); m_currentBrushWidget->setParent(this); m_currentBrushWidget->hide(); } widget->setMinimumSize(m_mininmumSize); m_currentBrushWidget = widget; m_layout->addWidget(widget); m_currentBrushWidget->show(); m_buttonGroup->button(m_chooserMap.key(widget))->setChecked(true); m_presetIsValid = (m_buttonGroup->checkedId() != CUSTOMBRUSH); } void KisBrushSelectionWidget::addChooser(const QString& text, QWidget* widget, int id, KoGroupButton::GroupPosition pos) { KoGroupButton * button = new KoGroupButton(this); button->setGroupPosition(pos); button->setText(text); button->setAutoRaise(true); button->setCheckable(true); uiWdgBrushChooser.brushChooserButtonLayout->addWidget(button); m_buttonGroup->addButton(button, id); m_chooserMap[m_buttonGroup->id(button)] = widget; widget->hide(); } void KisBrushSelectionWidget::setAutoPrecisionEnabled(int value) { m_precisionOption.setAutoPrecisionEnabled(value); if(m_precisionOption.autoPrecisionEnabled()) { m_precisionOption.setAutoPrecision(brush()->width()); setPrecisionEnabled(false); precisionChanged(m_precisionOption.precisionLevel()); uiWdgBrushChooser.label->setVisible(true); uiWdgBrushChooser.label_2->setVisible(true); uiWdgBrushChooser.deltaValueSpinBox->setVisible(true); uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(true); uiWdgBrushChooser.lblPrecisionValue->setVisible(true); uiWdgBrushChooser.lblPrecisionValue->setText("Precision:"+QString::number(m_precisionOption.precisionLevel())); } else { setPrecisionEnabled(true); uiWdgBrushChooser.label->setVisible(false); uiWdgBrushChooser.label_2->setVisible(false); uiWdgBrushChooser.deltaValueSpinBox->setVisible(false); uiWdgBrushChooser.sizeToStartFromSpinBox->setVisible(false); uiWdgBrushChooser.lblPrecisionValue->setVisible(false); } emit sigPrecisionChanged(); } void KisBrushSelectionWidget::setSizeToStartFrom(double value) { m_precisionOption.setSizeToStartFrom(value); emit sigPrecisionChanged(); } void KisBrushSelectionWidget::setDeltaValue(double value) { m_precisionOption.setDeltaValue(value); emit sigPrecisionChanged(); } #include "moc_kis_brush_selection_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_selection_widget.h b/plugins/paintops/libpaintop/kis_brush_selection_widget.h index 0c57868e2c..b68114d9e8 100644 --- a/plugins/paintops/libpaintop/kis_brush_selection_widget.h +++ b/plugins/paintops/libpaintop/kis_brush_selection_widget.h @@ -1,117 +1,117 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2014 Mohit Goyal * * 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_BRUSH_SELECTION_WIDGET_H #define KIS_BRUSH_SELECTION_WIDGET_H #include #include #include #include #include "kis_precision_option.h" #include "ui_wdgbrushchooser.h" class KisAutoBrushWidget; -class KisBrushChooser; +class KisPredefinedBrushChooser; class KisTextBrushChooser; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KisBrush; /** * Compound widget that collects all the various brush selection widgets. */ class PAINTOP_EXPORT KisBrushSelectionWidget : public QWidget { Q_OBJECT public: KisBrushSelectionWidget(QWidget * parent = 0); ~KisBrushSelectionWidget(); KisBrushSP brush() const; void setAutoBrush(bool on); void setPredefinedBrushes(bool on); void setCustomBrush(bool on); void setClipboardBrush(bool on); void setTextBrush(bool on); void setImage(KisImageWSP image); void setCurrentBrush(KisBrushSP brush); bool presetIsValid() { return m_presetIsValid; } void writeOptionSetting(KisPropertiesConfigurationSP settings) const; void readOptionSetting(const KisPropertiesConfigurationSP setting); void setPrecisionEnabled(bool value); bool autoPrecisionEnabled(); void hideOptions(const QStringList &options); Q_SIGNALS: void sigBrushChanged(); void sigPrecisionChanged(); private Q_SLOTS: void buttonClicked(int id); void precisionChanged(int value); void setAutoPrecisionEnabled(int value); void setSizeToStartFrom(double value); void setDeltaValue(double value); private: void setCurrentWidget(QWidget * widget); void addChooser(const QString & text, QWidget * widget, int id, KoGroupButton::GroupPosition pos); private: enum Type { AUTOBRUSH, PREDEFINEDBRUSH, CUSTOMBRUSH, TEXTBRUSH, CLIPBOARDBRUSH }; bool m_presetIsValid; Ui_WdgBrushChooser uiWdgBrushChooser; QGridLayout * m_layout; QWidget * m_currentBrushWidget; QHash m_chooserMap; QButtonGroup * m_buttonGroup; QSize m_mininmumSize; KisAutoBrushWidget * m_autoBrushWidget; - KisBrushChooser * m_brushChooser; + KisPredefinedBrushChooser * m_predefinedBrushWidget; KisTextBrushChooser * m_textBrushWidget; KisPrecisionOption m_precisionOption; }; #endif diff --git a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp index 51283df249..3d486c6cf7 100644 --- a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp +++ b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp @@ -1,128 +1,129 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spacing_selection_widget.h" #include #include #include "klocalizedstring.h" #include "kis_signals_blocker.h" #include "kis_slider_spin_box.h" struct KisSpacingSelectionWidget::Private { Private(KisSpacingSelectionWidget *_q) : q(_q), oldSliderValue(0.1) { } KisSpacingSelectionWidget *q; KisDoubleSliderSpinBox *slider; QCheckBox *autoButton; qreal oldSliderValue; void slotSpacingChanged(qreal value); void slotAutoSpacing(bool value); }; KisSpacingSelectionWidget::KisSpacingSelectionWidget(QWidget *parent) : QWidget(parent), m_d(new Private(this)) { m_d->slider = new KisDoubleSliderSpinBox(this); m_d->slider->setRange(0.0, 10.0, 2); m_d->slider->setSingleStep(0.01); m_d->slider->setValue(0.1); m_d->slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); m_d->autoButton = new QCheckBox(this); m_d->autoButton->setText(i18nc("@action:button", "Auto")); m_d->autoButton->setToolTip(i18nc("@info:tooltip", "In auto mode the spacing of the brush will be calculated automatically depending on its size")); m_d->autoButton->setCheckable(true); m_d->autoButton->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_d->autoButton); layout->addWidget(m_d->slider); + layout->setMargin(0); connect(m_d->slider, SIGNAL(valueChanged(qreal)), SLOT(slotSpacingChanged(qreal))); connect(m_d->autoButton, SIGNAL(toggled(bool)), SLOT(slotAutoSpacing(bool))); } KisSpacingSelectionWidget::~KisSpacingSelectionWidget() { } qreal KisSpacingSelectionWidget::spacing() const { return autoSpacingActive() ? 0.1 : m_d->slider->value(); } bool KisSpacingSelectionWidget::autoSpacingActive() const { return m_d->autoButton->isChecked(); } qreal KisSpacingSelectionWidget::autoSpacingCoeff() const { return autoSpacingActive() ? m_d->slider->value() : 1.0; } void KisSpacingSelectionWidget::setSpacing(bool isAuto, qreal spacing) { KisSignalsBlocker b1(m_d->autoButton); KisSignalsBlocker b2(m_d->slider); m_d->autoButton->setChecked(isAuto); m_d->slider->setValue(spacing); } void KisSpacingSelectionWidget::Private::slotSpacingChanged(qreal value) { Q_UNUSED(value); emit q->sigSpacingChanged(); } void KisSpacingSelectionWidget::Private::slotAutoSpacing(bool value) { qreal newSliderValue = 0.0; if (value) { newSliderValue = 1.0; oldSliderValue = slider->value(); } else { newSliderValue = oldSliderValue; } { KisSignalsBlocker b(slider); slider->setValue(newSliderValue); } emit q->sigSpacingChanged(); } #include "moc_kis_spacing_selection_widget.moc" diff --git a/plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui b/plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui index 259afc7667..3fb9057463 100644 --- a/plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui +++ b/plugins/paintops/libpaintop/sensors/SensorFadeConfiguration.ui @@ -1,90 +1,90 @@ SensorFadeConfiguration 0 0 148 55 0 0 0 0 0 0 repeat 0 0 Length: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 0 0 1 - 99999 + 5000 - 1000 + 200 KisSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui b/plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui index d05d662f8b..917f28eac0 100644 --- a/plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui +++ b/plugins/paintops/libpaintop/sensors/SensorTimeConfiguration.ui @@ -1,97 +1,90 @@ SensorTimeConfiguration 0 0 150 55 0 0 0 0 0 0 repeat 0 0 Duration: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + 0 0 - 1 + 0 - 9999 + 10 - 30 - - - - - - - s + 2 - KisSliderSpinBox + KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp index 9796720407..d8dd1dd7a9 100644 --- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp +++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_fade.cpp @@ -1,98 +1,102 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dynamic_sensor_fade.h" #include #include #include "ui_SensorFadeConfiguration.h" #include static const int DEFAULT_LENGTH = 1000; KisDynamicSensorFade::KisDynamicSensorFade() : KisDynamicSensor(FADE) , m_counter(0) , m_periodic(false) { setLength(DEFAULT_LENGTH); } qreal KisDynamicSensorFade::value(const KisPaintInformation& pi) { if (pi.isHoveringMode()) return 1.0; if (m_counter > m_length) { if (m_periodic) { reset(); } else { m_counter = m_length; } } qreal result = m_counter / qreal(m_length); m_counter++; return result; } void KisDynamicSensorFade::reset() { m_counter = 0; } void KisDynamicSensorFade::setPeriodic(bool periodic) { m_periodic = periodic; } void KisDynamicSensorFade::setLength(int length) { m_length = length; } QWidget* KisDynamicSensorFade::createConfigurationWidget(QWidget* parent, QWidget* ss) { QWidget* wdg = new QWidget(parent); Ui_SensorFadeConfiguration stc; stc.setupUi(wdg); stc.checkBoxRepeat->setChecked(m_periodic); + + stc.spinBoxLength->setSuffix(i18n(" px")); + stc.spinBoxLength->setExponentRatio(3.0); + connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool))); connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged())); stc.spinBoxLength->setValue(m_length); connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), SLOT(setLength(int))); connect(stc.spinBoxLength, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged())); return wdg; } void KisDynamicSensorFade::toXML(QDomDocument& doc, QDomElement& e) const { KisDynamicSensor::toXML(doc, e); e.setAttribute("periodic", m_periodic); e.setAttribute("length", m_length); } void KisDynamicSensorFade::fromXML(const QDomElement& e) { KisDynamicSensor::fromXML(e); m_periodic = e.attribute("periodic", "0").toInt(); m_length = e.attribute("length", QString::number(DEFAULT_LENGTH)).toInt(); } diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc index 82fb8e3d22..afba5a47c1 100644 --- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc +++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.cc @@ -1,105 +1,112 @@ /* * Copyright (c) 2007,2010 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dynamic_sensor_time.h" #include #include #include "ui_SensorTimeConfiguration.h" #include KisDynamicSensorTime::KisDynamicSensorTime() : KisDynamicSensor(TIME) , m_time(0) , m_periodic(true) , m_lastTime(0) { setLength(3); } qreal KisDynamicSensorTime::value(const KisPaintInformation& pi) { if (pi.isHoveringMode()) return 1.0; qreal curtime = pi.currentTime(); if (curtime >= m_lastTime) { m_time += curtime - m_lastTime; } else { // safely handle the situation when currentTime() < m_lastTime m_time = 0; } m_lastTime = curtime; if (m_time > m_length) { if (m_periodic) { m_time = m_time % m_length; } else { m_time = m_length; } } return m_time / qreal(m_length); } void KisDynamicSensorTime::reset() { m_lastTime = 0; m_time = 0; } void KisDynamicSensorTime::setPeriodic(bool periodic) { m_periodic = periodic; } -void KisDynamicSensorTime::setLength(int length) +void KisDynamicSensorTime::setLength(qreal length) { - m_length = length * 1000; + m_length = (int)(length * 1000); // convert to milliseconds } QWidget* KisDynamicSensorTime::createConfigurationWidget(QWidget* parent, QWidget* ss) { QWidget* wdg = new QWidget(parent); Ui_SensorTimeConfiguration stc; stc.setupUi(wdg); stc.checkBoxRepeat->setChecked(m_periodic); connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), SLOT(setPeriodic(bool))); connect(stc.checkBoxRepeat, SIGNAL(toggled(bool)), ss, SIGNAL(parametersChanged())); + + stc.spinBoxDuration->setRange(0.02, 10.0, 2); + stc.spinBoxDuration->setSuffix(i18n(" s")); + stc.spinBoxDuration->setValue(m_length / 1000); - connect(stc.spinBoxDuration, SIGNAL(valueChanged(int)), SLOT(setLength(int))); - connect(stc.spinBoxDuration, SIGNAL(valueChanged(int)), ss, SIGNAL(parametersChanged())); + connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), SLOT(setLength(qreal))); + connect(stc.spinBoxDuration, SIGNAL(valueChanged(qreal)), ss, SIGNAL(parametersChanged())); + + + return wdg; } void KisDynamicSensorTime::toXML(QDomDocument& doc, QDomElement& e) const { KisDynamicSensor::toXML(doc, e); e.setAttribute("periodic", m_periodic); e.setAttribute("duration", m_length); } void KisDynamicSensorTime::fromXML(const QDomElement& e) { KisDynamicSensor::fromXML(e); m_periodic = e.attribute("periodic", "0").toInt(); m_length = e.attribute("duration", "30").toInt(); } diff --git a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h index 13cea67b64..572bca2cb1 100644 --- a/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h +++ b/plugins/paintops/libpaintop/sensors/kis_dynamic_sensor_time.h @@ -1,52 +1,52 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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_DYNAMIC_SENSOR_TIME_H_ #define _KIS_DYNAMIC_SENSOR_TIME_H_ #include "kis_vec.h" #include "kis_dynamic_sensor.h" // class KisDynamicSensorTime : public QObject, public KisDynamicSensor { Q_OBJECT public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW using KisSerializableConfiguration::fromXML; using KisSerializableConfiguration::toXML; KisDynamicSensorTime(); virtual ~KisDynamicSensorTime() { } virtual qreal value(const KisPaintInformation&); virtual void reset(); virtual QWidget* createConfigurationWidget(QWidget* parent, QWidget*); public Q_SLOTS: virtual void setPeriodic(bool periodic); - virtual void setLength(int length); + virtual void setLength(qreal length); virtual void toXML(QDomDocument&, QDomElement&) const; virtual void fromXML(const QDomElement&); private: int m_time; bool m_periodic; int m_lastTime; }; #endif diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp index 23ef45af46..3c453a2110 100644 --- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp +++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyOptionWidget.cpp @@ -1,632 +1,614 @@ /* This file is part of the KDE project Copyright (C) 2008 Fela Winkelmolen 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 "KarbonCalligraphyOptionWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" /* Profiles are saved in karboncalligraphyrc In the group "General", profile is the name of profile used Every profile is described in a group, the name of which is "ProfileN" Starting to count from 0 onwards (NOTE: the index in profiles is different from the N) Default profiles are added by the function addDefaultProfiles(), once they have been added, the entry defaultProfilesAdded in the "General" group is set to true TODO: add a reset defaults option? */ // name of the configuration file const QString RCFILENAME = "karboncalligraphyrc"; KarbonCalligraphyOptionWidget::KarbonCalligraphyOptionWidget() : m_changingProfile(false) { QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_comboBox = new KComboBox(this); layout->addWidget(m_comboBox, 0, 0); m_saveButton = new QToolButton(this); m_saveButton->setToolTip(i18n("Save profile as...")); m_saveButton->setIcon(koIcon("document-save-as")); layout->addWidget(m_saveButton, 0, 1); m_removeButton = new QToolButton(this); m_removeButton->setToolTip(i18n("Remove profile")); m_removeButton->setIcon(koIcon("list-remove")); layout->addWidget(m_removeButton, 0, 2); QGridLayout *detailsLayout = new QGridLayout(); detailsLayout->setContentsMargins(0, 0, 0, 0); detailsLayout->setVerticalSpacing(0); m_usePath = new QCheckBox(i18n("&Follow selected path"), this); detailsLayout->addWidget(m_usePath, 0, 0, 1, 4); m_usePressure = new QCheckBox(i18n("Use tablet &pressure"), this); detailsLayout->addWidget(m_usePressure, 1, 0, 1, 4); QLabel *widthLabel = new QLabel(i18n("Width:"), this); widthLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_widthBox = new KisDoubleParseSpinBox(this); m_widthBox->setRange(0.0, 999.0); widthLabel->setBuddy(m_widthBox); detailsLayout->addWidget(widthLabel, 2, 2); detailsLayout->addWidget(m_widthBox, 2, 3); QLabel *thinningLabel = new QLabel(i18n("Thinning:"), this); thinningLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_thinningBox = new KisDoubleParseSpinBox(this); m_thinningBox->setRange(-1.0, 1.0); m_thinningBox->setSingleStep(0.1); thinningLabel->setBuddy(m_thinningBox); detailsLayout->addWidget(thinningLabel, 2, 0); detailsLayout->addWidget(m_thinningBox, 2, 1); m_useAngle = new QCheckBox(i18n("Use tablet &angle"), this); detailsLayout->addWidget(m_useAngle, 3, 0, 1, 4); QLabel *angleLabel = new QLabel(i18n("Angle:"), this); angleLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_angleBox = new KisIntParseSpinBox(this); m_angleBox->setRange(0, 179); m_angleBox->setWrapping(true); angleLabel->setBuddy(m_angleBox); detailsLayout->addWidget(angleLabel, 4, 0); detailsLayout->addWidget(m_angleBox, 4, 1); QLabel *fixationLabel = new QLabel(i18n("Fixation:"), this); fixationLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_fixationBox = new KisDoubleParseSpinBox(this); m_fixationBox->setRange(0.0, 1.0); m_fixationBox->setSingleStep(0.1); fixationLabel->setBuddy(m_fixationBox); detailsLayout->addWidget(fixationLabel, 5, 0); detailsLayout->addWidget(m_fixationBox, 5, 1); QLabel *capsLabel = new QLabel(i18n("Caps:"), this); capsLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_capsBox = new KisDoubleParseSpinBox(this); m_capsBox->setRange(0.0, 2.0); m_capsBox->setSingleStep(0.03); capsLabel->setBuddy(m_capsBox); detailsLayout->addWidget(capsLabel, 5, 2); detailsLayout->addWidget(m_capsBox, 5, 3); QLabel *massLabel = new QLabel(i18n("Mass:"), this); massLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_massBox = new KisDoubleParseSpinBox(this); m_massBox->setRange(0.0, 20.0); m_massBox->setDecimals(1); massLabel->setBuddy(m_massBox); detailsLayout->addWidget(massLabel, 6, 0); detailsLayout->addWidget(m_massBox, 6, 1); QLabel *dragLabel = new QLabel(i18n("Drag:"), this); dragLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); m_dragBox = new KisDoubleParseSpinBox(this); m_dragBox->setRange(0.0, 1.0); m_dragBox->setSingleStep(0.1); dragLabel->setBuddy(m_dragBox); detailsLayout->addWidget(dragLabel, 6, 2); detailsLayout->addWidget(m_dragBox, 6, 3); layout->addLayout(detailsLayout, 1, 0, 1, 3); layout->setRowStretch(2, 1); createConnections(); addDefaultProfiles(); // if they are already added does nothing loadProfiles(); } KarbonCalligraphyOptionWidget::~KarbonCalligraphyOptionWidget() { qDeleteAll(m_profiles); - qDebug() << "dtor!!!!"; } void KarbonCalligraphyOptionWidget::emitAll() { emit usePathChanged(m_usePath->isChecked()); emit usePressureChanged(m_usePressure->isChecked()); emit useAngleChanged(m_useAngle->isChecked()); emit widthChanged(m_widthBox->value()); emit thinningChanged(m_thinningBox->value()); emit angleChanged(m_angleBox->value()); emit fixationChanged(m_fixationBox->value()); emit capsChanged(m_capsBox->value()); emit massChanged(m_massBox->value()); emit dragChanged(m_dragBox->value()); } void KarbonCalligraphyOptionWidget::loadProfile(const QString &name) { if (m_changingProfile) { return; } - qDebug() << "trying profile" << name; // write the new profile in the config file KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); generalGroup.writeEntry("profile", name); config.sync(); // and load it loadCurrentProfile(); // don't show Current if it isn't selected if (name != i18n("Current")) { removeProfile(i18n("Current")); } } void KarbonCalligraphyOptionWidget::updateCurrentProfile() { if (!m_changingProfile) { saveProfile("Current"); } } void KarbonCalligraphyOptionWidget::saveProfileAs() { QString name; // loop until a valid name is entered or the user cancelled while (1) { bool ok; name = QInputDialog::getText(this, i18n("Profile name"), i18n("Please insert the name by which " "you want to save this profile:"), QLineEdit::Normal, QString(), &ok); if (!ok) { return; } if (name.isEmpty() || name == i18n("Current")) { KMessageBox::sorry(this, i18n("Sorry, the name you entered is invalid."), i18nc("invalid profile name", "Invalid name.")); // try again saveProfileAs(); continue; // ask again } if (m_profiles.contains(name)) { int ret = KMessageBox::warningYesNo(this, i18n("A profile with that name already exists.\n" "Do you want to overwrite it?")); if (ret == KMessageBox::Yes) { break; // exit while loop (save profile) } // else ask again } else { // the name is valid break; // exit while loop (save profile) } } saveProfile(name); } void KarbonCalligraphyOptionWidget::removeProfile() { removeProfile(m_comboBox->currentText()); } void KarbonCalligraphyOptionWidget::toggleUseAngle(bool checked) { m_angleBox->setEnabled(! checked); } void KarbonCalligraphyOptionWidget::increaseWidth() { m_widthBox->setValue(m_widthBox->value() + 1); } void KarbonCalligraphyOptionWidget::decreaseWidth() { m_widthBox->setValue(m_widthBox->value() - 1); } void KarbonCalligraphyOptionWidget::increaseAngle() { m_angleBox->setValue((m_angleBox->value() + 3) % 180); } void KarbonCalligraphyOptionWidget::decreaseAngle() { m_angleBox->setValue((m_angleBox->value() - 3) % 180); } /****************************************************************************** ************************* Convenience Functions ****************************** ******************************************************************************/ void KarbonCalligraphyOptionWidget::createConnections() { connect(m_comboBox, SIGNAL(currentIndexChanged(QString)), SLOT(loadProfile(QString))); // propagate changes connect(m_usePath, SIGNAL(toggled(bool)), SIGNAL(usePathChanged(bool))); connect(m_usePressure, SIGNAL(toggled(bool)), SIGNAL(usePressureChanged(bool))); connect(m_useAngle, SIGNAL(toggled(bool)), SIGNAL(useAngleChanged(bool))); connect(m_widthBox, SIGNAL(valueChanged(double)), SIGNAL(widthChanged(double))); connect(m_thinningBox, SIGNAL(valueChanged(double)), SIGNAL(thinningChanged(double))); connect(m_angleBox, SIGNAL(valueChanged(int)), SIGNAL(angleChanged(int))); connect(m_fixationBox, SIGNAL(valueChanged(double)), SIGNAL(fixationChanged(double))); connect(m_capsBox, SIGNAL(valueChanged(double)), SIGNAL(capsChanged(double))); connect(m_massBox, SIGNAL(valueChanged(double)), SIGNAL(massChanged(double))); connect(m_dragBox, SIGNAL(valueChanged(double)), SIGNAL(dragChanged(double))); // update profile connect(m_usePath, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); connect(m_usePressure, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); connect(m_useAngle, SIGNAL(toggled(bool)), SLOT(updateCurrentProfile())); connect(m_widthBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_thinningBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_angleBox, SIGNAL(valueChanged(int)), SLOT(updateCurrentProfile())); connect(m_fixationBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_capsBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_massBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_dragBox, SIGNAL(valueChanged(double)), SLOT(updateCurrentProfile())); connect(m_saveButton, SIGNAL(clicked()), SLOT(saveProfileAs())); connect(m_removeButton, SIGNAL(clicked()), SLOT(removeProfile())); // visualization connect(m_useAngle, SIGNAL(toggled(bool)), SLOT(toggleUseAngle(bool))); } void KarbonCalligraphyOptionWidget::addDefaultProfiles() { // check if the profiles where already added KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); if (generalGroup.readEntry("defaultProfilesAdded", false)) { return; } KConfigGroup profile0(&config, "Profile0"); profile0.writeEntry("name", i18n("Mouse")); profile0.writeEntry("usePath", false); profile0.writeEntry("usePressure", false); profile0.writeEntry("useAngle", false); profile0.writeEntry("width", 30.0); profile0.writeEntry("thinning", 0.2); profile0.writeEntry("angle", 30); profile0.writeEntry("fixation", 1.0); profile0.writeEntry("caps", 0.0); profile0.writeEntry("mass", 3.0); profile0.writeEntry("drag", 0.7); KConfigGroup profile1(&config, "Profile1"); profile1.writeEntry("name", i18n("Graphics Pen")); profile1.writeEntry("width", 50.0); profile1.writeEntry("usePath", false); profile1.writeEntry("usePressure", false); profile1.writeEntry("useAngle", false); profile1.writeEntry("thinning", 0.2); profile1.writeEntry("angle", 30); profile1.writeEntry("fixation", 1.0); profile1.writeEntry("caps", 0.0); profile1.writeEntry("mass", 1.0); profile1.writeEntry("drag", 0.9); generalGroup.writeEntry("profile", i18n("Mouse")); generalGroup.writeEntry("defaultProfilesAdded", true); config.sync(); } void KarbonCalligraphyOptionWidget::loadProfiles() { KConfig config(RCFILENAME); // load profiles as long as they are present int i = 0; while (1) { // forever KConfigGroup profileGroup(&config, "Profile" + QString::number(i)); // invalid profile, assume we reached the last one if (!profileGroup.hasKey("name")) { break; } Profile *profile = new Profile; profile->index = i; profile->name = profileGroup.readEntry("name", QString()); profile->usePath = profileGroup.readEntry("usePath", false); profile->usePressure = profileGroup.readEntry("usePressure", false); profile->useAngle = profileGroup.readEntry("useAngle", false); profile->width = profileGroup.readEntry("width", 30.0); profile->thinning = profileGroup.readEntry("thinning", 0.2); profile->angle = profileGroup.readEntry("angle", 30); profile->fixation = profileGroup.readEntry("fixation", 0.0); profile->caps = profileGroup.readEntry("caps", 0.0); profile->mass = profileGroup.readEntry("mass", 3.0); profile->drag = profileGroup.readEntry("drag", 0.7); m_profiles.insert(profile->name, profile); ++i; } m_changingProfile = true; ProfileMap::const_iterator it = m_profiles.constBegin(); ProfileMap::const_iterator lastIt = m_profiles.constEnd(); for (; it != lastIt; ++it) { m_comboBox->addItem(it.key()); } m_changingProfile = false; loadCurrentProfile(); } void KarbonCalligraphyOptionWidget::loadCurrentProfile() { KConfig config(RCFILENAME); KConfigGroup generalGroup(&config, "General"); QString currentProfile = generalGroup.readEntry("profile", QString()); - qDebug() << currentProfile; // find the index needed by the comboBox int index = profilePosition(currentProfile); if (currentProfile.isEmpty() || index < 0) { - qDebug() << "invalid karboncalligraphyrc!!" << currentProfile << index; return; } - qDebug() << m_comboBox->currentIndex() << index; m_comboBox->setCurrentIndex(index); Profile *profile = m_profiles[currentProfile]; m_changingProfile = true; m_usePath->setChecked(profile->usePath); m_usePressure->setChecked(profile->usePressure); m_useAngle->setChecked(profile->useAngle); m_widthBox->setValue(profile->width); m_thinningBox->setValue(profile->thinning); m_angleBox->setValue(profile->angle); m_fixationBox->setValue(profile->fixation); m_capsBox->setValue(profile->caps); m_massBox->setValue(profile->mass); m_dragBox->setValue(profile->drag); m_changingProfile = false; } void KarbonCalligraphyOptionWidget::saveProfile(const QString &name) { - qDebug() << name; Profile *profile = new Profile; profile->name = name; profile->usePath = m_usePath->isChecked(); profile->usePressure = m_usePressure->isChecked(); profile->useAngle = m_useAngle->isChecked(); profile->width = m_widthBox->value(); profile->thinning = m_thinningBox->value(); profile->angle = m_angleBox->value(); profile->fixation = m_fixationBox->value(); profile->caps = m_capsBox->value(); profile->mass = m_massBox->value(); profile->drag = m_dragBox->value(); if (m_profiles.contains(name)) { // there is already a profile with the same name, overwrite profile->index = m_profiles[name]->index; m_profiles.insert(name, profile); } else { // it is a new profile profile->index = m_profiles.count(); m_profiles.insert(name, profile); // add the profile to the combobox - qDebug() << "BEFORE:"; QString dbg; for (int i = 0; i < m_comboBox->count(); ++i) { dbg += m_comboBox->itemText(i) + ' '; } - qDebug() << dbg; int pos = profilePosition(name); m_changingProfile = true; m_comboBox->insertItem(pos, name); m_changingProfile = false; - qDebug() << "AFTER:"; for (int i = 0; i < m_comboBox->count(); ++i) { dbg += m_comboBox->itemText(i) + ' '; } - qDebug() << dbg; - qDebug() << "new at" << pos << m_comboBox->itemText(pos) << name; } KConfig config(RCFILENAME); QString str = "Profile" + QString::number(profile->index); KConfigGroup profileGroup(&config, str); profileGroup.writeEntry("name", name); profileGroup.writeEntry("usePath", profile->usePath); profileGroup.writeEntry("usePressure", profile->usePressure); profileGroup.writeEntry("useAngle", profile->useAngle); profileGroup.writeEntry("width", profile->width); profileGroup.writeEntry("thinning", profile->thinning); profileGroup.writeEntry("angle", profile->angle); profileGroup.writeEntry("fixation", profile->fixation); profileGroup.writeEntry("caps", profile->caps); profileGroup.writeEntry("mass", profile->mass); profileGroup.writeEntry("drag", profile->drag); KConfigGroup generalGroup(&config, "General"); generalGroup.writeEntry("profile", name); config.sync(); - qDebug() << name; - int pos = profilePosition(name); - qDebug() << "adding in" << pos << m_comboBox->itemText(pos); m_comboBox->setCurrentIndex(profilePosition(name)); - qDebug() << m_comboBox->currentText(); } void KarbonCalligraphyOptionWidget::removeProfile(const QString &name) { - qDebug() << "removing profile" << name; - int index = profilePosition(name); if (index < 0) { return; // no such profile } // remove the file from the config file KConfig config(RCFILENAME); int deletedIndex = m_profiles[name]->index; QString deletedGroup = "Profile" + QString::number(deletedIndex); - qDebug() << deletedGroup; config.deleteGroup(deletedGroup); config.sync(); // and from profiles m_profiles.remove(name); m_comboBox->removeItem(index); // now in the config file there is value ProfileN missing, // where N = configIndex, so put the last one there if (m_profiles.isEmpty()) { return; } int lastN = -1; Profile *profile = 0; // profile to be moved, will be the last one Q_FOREACH (Profile *p, m_profiles) { if (p->index > lastN) { lastN = p->index; profile = p; } } Q_ASSERT(profile != 0); // do nothing if the deleted group was the last one if (deletedIndex > lastN) { return; } QString lastGroup = "Profile" + QString::number(lastN); config.deleteGroup(lastGroup); KConfigGroup profileGroup(&config, deletedGroup); profileGroup.writeEntry("name", profile->name); profileGroup.writeEntry("usePath", profile->usePath); profileGroup.writeEntry("usePressure", profile->usePressure); profileGroup.writeEntry("useAngle", profile->useAngle); profileGroup.writeEntry("width", profile->width); profileGroup.writeEntry("thinning", profile->thinning); profileGroup.writeEntry("angle", profile->angle); profileGroup.writeEntry("fixation", profile->fixation); profileGroup.writeEntry("caps", profile->caps); profileGroup.writeEntry("mass", profile->mass); profileGroup.writeEntry("drag", profile->drag); config.sync(); profile->index = deletedIndex; } int KarbonCalligraphyOptionWidget::profilePosition(const QString &profileName) { int res = 0; ProfileMap::const_iterator it = m_profiles.constBegin(); ProfileMap::const_iterator lastIt = m_profiles.constEnd(); for (; it != lastIt; ++it) { if (it.key() == profileName) { return res; } ++res; } return -1; } void KarbonCalligraphyOptionWidget::setUsePathEnabled(bool enabled) { m_usePath->setEnabled(enabled); } diff --git a/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp index a8492f0cff..f1b7b61d48 100644 --- a/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp @@ -1,696 +1,722 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_free_transform_strategy.h" #include #include #include #include #include "kis_coordinates_converter.h" #include "tool_transform_args.h" #include "transform_transaction_properties.h" #include "krita_utils.h" #include "kis_cursor.h" #include "kis_transform_utils.h" #include "kis_free_transform_strategy_gsl_helpers.h" enum StrokeFunction { ROTATE = 0, MOVE, RIGHTSCALE, TOPRIGHTSCALE, TOPSCALE, TOPLEFTSCALE, LEFTSCALE, BOTTOMLEFTSCALE, BOTTOMSCALE, BOTTOMRIGHTSCALE, BOTTOMSHEAR, RIGHTSHEAR, TOPSHEAR, LEFTSHEAR, MOVECENTER, PERSPECTIVE }; struct KisFreeTransformStrategy::Private { Private(KisFreeTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction) : q(_q), converter(_converter), currentArgs(_currentArgs), transaction(_transaction), imageTooBig(false) { scaleCursors[0] = KisCursor::sizeHorCursor(); scaleCursors[1] = KisCursor::sizeFDiagCursor(); scaleCursors[2] = KisCursor::sizeVerCursor(); scaleCursors[3] = KisCursor::sizeBDiagCursor(); scaleCursors[4] = KisCursor::sizeHorCursor(); scaleCursors[5] = KisCursor::sizeFDiagCursor(); scaleCursors[6] = KisCursor::sizeVerCursor(); scaleCursors[7] = KisCursor::sizeBDiagCursor(); shearCursorPixmap.load(":/shear_cursor.png"); } KisFreeTransformStrategy *q; /// standard members /// const KisCoordinatesConverter *converter; ////// ToolTransformArgs ¤tArgs; ////// TransformTransactionProperties &transaction; QTransform thumbToImageTransform; QImage originalImage; QTransform paintingTransform; QPointF paintingOffset; QTransform handlesTransform; /// custom members /// StrokeFunction function; struct HandlePoints { QPointF topLeft; QPointF topMiddle; QPointF topRight; QPointF middleLeft; QPointF rotationCenter; QPointF middleRight; QPointF bottomLeft; QPointF bottomMiddle; QPointF bottomRight; }; HandlePoints transformedHandles; QTransform transform; QCursor scaleCursors[8]; // cursors for the 8 directions QPixmap shearCursorPixmap; bool imageTooBig; ToolTransformArgs clickArgs; QPointF clickPos; QCursor getScaleCursor(const QPointF &handlePt); QCursor getShearCursor(const QPointF &start, const QPointF &end); void recalculateTransformations(); void recalculateTransformedHandles(); }; KisFreeTransformStrategy::KisFreeTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction) : KisSimplifiedActionPolicyStrategy(converter, snapGuide), m_d(new Private(this, converter, currentArgs, transaction)) { } KisFreeTransformStrategy::~KisFreeTransformStrategy() { } void KisFreeTransformStrategy::Private::recalculateTransformedHandles() { transformedHandles.topLeft = transform.map(transaction.originalTopLeft()); transformedHandles.topMiddle = transform.map(transaction.originalMiddleTop()); transformedHandles.topRight = transform.map(transaction.originalTopRight()); transformedHandles.middleLeft = transform.map(transaction.originalMiddleLeft()); transformedHandles.rotationCenter = transform.map(currentArgs.originalCenter() + currentArgs.rotationCenterOffset()); transformedHandles.middleRight = transform.map(transaction.originalMiddleRight()); transformedHandles.bottomLeft = transform.map(transaction.originalBottomLeft()); transformedHandles.bottomMiddle = transform.map(transaction.originalMiddleBottom()); transformedHandles.bottomRight = transform.map(transaction.originalBottomRight()); } void KisFreeTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) { if (perspectiveModifierActive) { m_d->function = PERSPECTIVE; return; } QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect())); qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter); qreal rotationHandleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter); StrokeFunction defaultFunction = transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : ROTATE; KisTransformUtils::HandleChooser handleChooser(mousePos, defaultFunction); handleChooser.addFunction(m_d->transformedHandles.topMiddle, handleRadius, TOPSCALE); handleChooser.addFunction(m_d->transformedHandles.topRight, handleRadius, TOPRIGHTSCALE); handleChooser.addFunction(m_d->transformedHandles.middleRight, handleRadius, RIGHTSCALE); handleChooser.addFunction(m_d->transformedHandles.bottomRight, handleRadius, BOTTOMRIGHTSCALE); handleChooser.addFunction(m_d->transformedHandles.bottomMiddle, handleRadius, BOTTOMSCALE); handleChooser.addFunction(m_d->transformedHandles.bottomLeft, handleRadius, BOTTOMLEFTSCALE); handleChooser.addFunction(m_d->transformedHandles.middleLeft, handleRadius, LEFTSCALE); handleChooser.addFunction(m_d->transformedHandles.topLeft, handleRadius, TOPLEFTSCALE); handleChooser.addFunction(m_d->transformedHandles.rotationCenter, rotationHandleRadius, MOVECENTER); m_d->function = handleChooser.function(); if (m_d->function == ROTATE || m_d->function == MOVE) { QRectF originalRect = m_d->transaction.originalRect(); QPointF t = m_d->transform.inverted().map(mousePos); if (t.x() >= originalRect.left() && t.x() <= originalRect.right()) { if (fabs(t.y() - originalRect.top()) <= handleRadius) m_d->function = TOPSHEAR; if (fabs(t.y() - originalRect.bottom()) <= handleRadius) m_d->function = BOTTOMSHEAR; } if (t.y() >= originalRect.top() && t.y() <= originalRect.bottom()) { if (fabs(t.x() - originalRect.left()) <= handleRadius) m_d->function = LEFTSHEAR; if (fabs(t.x() - originalRect.right()) <= handleRadius) m_d->function = RIGHTSHEAR; } } } QCursor KisFreeTransformStrategy::Private::getScaleCursor(const QPointF &handlePt) { QPointF handlePtInWidget = converter->imageToWidget(handlePt); QPointF centerPtInWidget = converter->imageToWidget(currentArgs.transformedCenter()); QPointF direction = handlePtInWidget - centerPtInWidget; qreal angle = atan2(direction.y(), direction.x()); angle = normalizeAngle(angle); int octant = qRound(angle * 4. / M_PI) % 8; return scaleCursors[octant]; } QCursor KisFreeTransformStrategy::Private::getShearCursor(const QPointF &start, const QPointF &end) { QPointF startPtInWidget = converter->imageToWidget(start); QPointF endPtInWidget = converter->imageToWidget(end); QPointF direction = endPtInWidget - startPtInWidget; qreal angle = atan2(-direction.y(), direction.x()); return QCursor(shearCursorPixmap.transformed(QTransform().rotateRadians(-angle))); } QCursor KisFreeTransformStrategy::getCurrentCursor() const { QCursor cursor; switch (m_d->function) { case MOVE: cursor = KisCursor::moveCursor(); break; case ROTATE: cursor = KisCursor::rotateCursor(); break; case PERSPECTIVE: //TODO: find another cursor for perspective cursor = KisCursor::rotateCursor(); break; case RIGHTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.middleRight); break; case TOPSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.topMiddle); break; case LEFTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.middleLeft); break; case BOTTOMSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomMiddle); break; case TOPRIGHTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.topRight); break; case BOTTOMLEFTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomLeft); break; case TOPLEFTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.topLeft); break; case BOTTOMRIGHTSCALE: cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomRight); break; case MOVECENTER: cursor = KisCursor::handCursor(); break; case BOTTOMSHEAR: cursor = m_d->getShearCursor(m_d->transformedHandles.bottomLeft, m_d->transformedHandles.bottomRight); break; case RIGHTSHEAR: cursor = m_d->getShearCursor(m_d->transformedHandles.bottomRight, m_d->transformedHandles.topRight); break; case TOPSHEAR: cursor = m_d->getShearCursor(m_d->transformedHandles.topRight, m_d->transformedHandles.topLeft); break; case LEFTSHEAR: cursor = m_d->getShearCursor(m_d->transformedHandles.topLeft, m_d->transformedHandles.bottomLeft); break; } return cursor; } void KisFreeTransformStrategy::paint(QPainter &gc) { gc.save(); gc.setOpacity(m_d->transaction.basePreviewOpacity()); gc.setTransform(m_d->paintingTransform, true); gc.drawImage(m_d->paintingOffset, originalImage()); gc.restore(); // Draw Handles QRectF handleRect = KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius, m_d->handlesTransform, m_d->transaction.originalRect(), 0, 0); qreal rX = 1; qreal rY = 1; QRectF rotationCenterRect = KisTransformUtils::handleRect(KisTransformUtils::rotationHandleVisualRadius, m_d->handlesTransform, m_d->transaction.originalRect(), &rX, &rY); QPainterPath handles; handles.moveTo(m_d->transaction.originalTopLeft()); handles.lineTo(m_d->transaction.originalTopRight()); handles.lineTo(m_d->transaction.originalBottomRight()); handles.lineTo(m_d->transaction.originalBottomLeft()); handles.lineTo(m_d->transaction.originalTopLeft()); handles.addRect(handleRect.translated(m_d->transaction.originalTopLeft())); handles.addRect(handleRect.translated(m_d->transaction.originalTopRight())); handles.addRect(handleRect.translated(m_d->transaction.originalBottomLeft())); handles.addRect(handleRect.translated(m_d->transaction.originalBottomRight())); handles.addRect(handleRect.translated(m_d->transaction.originalMiddleLeft())); handles.addRect(handleRect.translated(m_d->transaction.originalMiddleRight())); handles.addRect(handleRect.translated(m_d->transaction.originalMiddleTop())); handles.addRect(handleRect.translated(m_d->transaction.originalMiddleBottom())); QPointF rotationCenter = m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset(); QPointF dx(rX + 3, 0); QPointF dy(0, rY + 3); handles.addEllipse(rotationCenterRect.translated(rotationCenter)); handles.moveTo(rotationCenter - dx); handles.lineTo(rotationCenter + dx); handles.moveTo(rotationCenter - dy); handles.lineTo(rotationCenter + dy); gc.save(); //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this! QPainterPath mappedHandles = m_d->handlesTransform.map(handles); QPen pen[2]; pen[0].setWidth(1); pen[1].setWidth(2); pen[1].setColor(Qt::lightGray); for (int i = 1; i >= 0; --i) { gc.setPen(pen[i]); gc.drawPath(mappedHandles); } gc.restore(); } void KisFreeTransformStrategy::externalConfigChanged() { m_d->recalculateTransformations(); } bool KisFreeTransformStrategy::beginPrimaryAction(const QPointF &pt) { m_d->clickArgs = m_d->currentArgs; m_d->clickPos = pt; return true; } -void KisFreeTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool specialModifierActive) +void KisFreeTransformStrategy::continuePrimaryAction(const QPointF &mousePos, + bool shiftModifierActive, + bool altModifierActive) { - // Note: "specialModifierActive" just tells us if the shift key is being pressed + // Note: "shiftModifierActive" just tells us if the shift key is being pressed + // Note: "altModifierActive" just tells us if the alt key is being pressed + + const QPointF anchorPoint = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset(); switch (m_d->function) { case MOVE: { QPointF diff = mousePos - m_d->clickPos; - if (specialModifierActive) { + if (shiftModifierActive) { KisTransformUtils::MatricesPack m(m_d->clickArgs); QTransform t = m.S * m.projectedP; QPointF originalDiff = t.inverted().map(diff); if (qAbs(originalDiff.x()) >= qAbs(originalDiff.y())) { originalDiff.setY(0); } else { originalDiff.setX(0); } diff = t.map(originalDiff); } m_d->currentArgs.setTransformedCenter(m_d->clickArgs.transformedCenter() + diff); break; } case ROTATE: { KisTransformUtils::MatricesPack clickM(m_d->clickArgs); QTransform clickT = clickM.finalTransform(); QPointF rotationCenter = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset(); QPointF clickMouseImagePos = clickT.inverted().map(m_d->clickPos) - rotationCenter; QPointF mouseImagePos = clickT.inverted().map(mousePos) - rotationCenter; qreal a1 = atan2(clickMouseImagePos.y(), clickMouseImagePos.x()); qreal a2 = atan2(mouseImagePos.y(), mouseImagePos.x()); qreal theta = a2 - a1; // Snap with shift key - if (specialModifierActive) { + if (shiftModifierActive) { const qreal snapAngle = M_PI_4 / 6.0; // fifteen degrees qint32 thetaIndex = static_cast((theta / snapAngle) + 0.5); m_d->currentArgs.setAZ(normalizeAngle(thetaIndex * snapAngle)); } else { m_d->currentArgs.setAZ(normalizeAngle(m_d->clickArgs.aZ() + theta)); } KisTransformUtils::MatricesPack m(m_d->currentArgs); QTransform t = m.finalTransform(); QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset()); QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset()); m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter); } break; case PERSPECTIVE: { QPointF diff = mousePos - m_d->clickPos; double thetaX = - diff.y() * M_PI / m_d->transaction.originalHalfHeight() / 2 / fabs(m_d->currentArgs.scaleY()); m_d->currentArgs.setAX(normalizeAngle(m_d->clickArgs.aX() + thetaX)); qreal sign = qAbs(m_d->currentArgs.aX() - M_PI) < M_PI / 2 ? -1.0 : 1.0; double thetaY = sign * diff.x() * M_PI / m_d->transaction.originalHalfWidth() / 2 / fabs(m_d->currentArgs.scaleX()); m_d->currentArgs.setAY(normalizeAngle(m_d->clickArgs.aY() + thetaY)); KisTransformUtils::MatricesPack m(m_d->currentArgs); QTransform t = m.finalTransform(); QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset()); KisTransformUtils::MatricesPack clickM(m_d->clickArgs); QTransform clickT = clickM.finalTransform(); QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset()); m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter); } break; case TOPSCALE: case BOTTOMSCALE: { QPointF staticPoint; QPointF movingPoint; qreal extraSign; if (m_d->function == TOPSCALE) { - staticPoint = m_d->transaction.originalMiddleBottom(); movingPoint = m_d->transaction.originalMiddleTop(); extraSign = -1.0; } else { staticPoint = m_d->transaction.originalMiddleTop(); movingPoint = m_d->transaction.originalMiddleBottom(); extraSign = 1.0; } + // override scale static point if it is locked + if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) && + !qFuzzyCompare(anchorPoint.y(), movingPoint.y())) { + + staticPoint = anchorPoint; + } + QPointF mouseImagePos = m_d->transform.inverted().map(mousePos); qreal sign = mouseImagePos.y() <= staticPoint.y() ? -extraSign : extraSign; m_d->currentArgs.setScaleY(sign * m_d->currentArgs.scaleY()); QPointF staticPointInView = m_d->transform.map(staticPoint); qreal dist = kisDistance(staticPointInView, mousePos); GSL::ScaleResult1D result = GSL::calculateScaleY(m_d->currentArgs, staticPoint, staticPointInView, movingPoint, dist); - if (specialModifierActive || m_d->currentArgs.keepAspectRatio()) { + if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) { qreal aspectRatio = m_d->clickArgs.scaleX() / m_d->clickArgs.scaleY(); m_d->currentArgs.setScaleX(aspectRatio * result.scale); } m_d->currentArgs.setScaleY(result.scale); m_d->currentArgs.setTransformedCenter(result.transformedCenter); break; } case LEFTSCALE: case RIGHTSCALE: { QPointF staticPoint; QPointF movingPoint; qreal extraSign; if (m_d->function == LEFTSCALE) { staticPoint = m_d->transaction.originalMiddleRight(); movingPoint = m_d->transaction.originalMiddleLeft(); extraSign = -1.0; } else { staticPoint = m_d->transaction.originalMiddleLeft(); movingPoint = m_d->transaction.originalMiddleRight(); extraSign = 1.0; } + // override scale static point if it is locked + if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) && + !qFuzzyCompare(anchorPoint.x(), movingPoint.x())) { + + staticPoint = anchorPoint; + } + QPointF mouseImagePos = m_d->transform.inverted().map(mousePos); qreal sign = mouseImagePos.x() <= staticPoint.x() ? -extraSign : extraSign; m_d->currentArgs.setScaleX(sign * m_d->currentArgs.scaleX()); QPointF staticPointInView = m_d->transform.map(staticPoint); qreal dist = kisDistance(staticPointInView, mousePos); GSL::ScaleResult1D result = GSL::calculateScaleX(m_d->currentArgs, staticPoint, staticPointInView, movingPoint, dist); - if (specialModifierActive || m_d->currentArgs.keepAspectRatio()) { + if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) { qreal aspectRatio = m_d->clickArgs.scaleY() / m_d->clickArgs.scaleX(); m_d->currentArgs.setScaleY(aspectRatio * result.scale); } m_d->currentArgs.setScaleX(result.scale); m_d->currentArgs.setTransformedCenter(result.transformedCenter); break; } case TOPRIGHTSCALE: case BOTTOMRIGHTSCALE: case TOPLEFTSCALE: case BOTTOMLEFTSCALE: { QPointF staticPoint; QPointF movingPoint; if (m_d->function == TOPRIGHTSCALE) { staticPoint = m_d->transaction.originalBottomLeft(); movingPoint = m_d->transaction.originalTopRight(); } else if (m_d->function == BOTTOMRIGHTSCALE) { staticPoint = m_d->transaction.originalTopLeft(); movingPoint = m_d->transaction.originalBottomRight(); } else if (m_d->function == TOPLEFTSCALE) { staticPoint = m_d->transaction.originalBottomRight(); movingPoint = m_d->transaction.originalTopLeft(); } else { staticPoint = m_d->transaction.originalTopRight(); movingPoint = m_d->transaction.originalBottomLeft(); } + // override scale static point if it is locked + if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) && + !(qFuzzyCompare(anchorPoint.x(), movingPoint.x()) || + qFuzzyCompare(anchorPoint.y(), movingPoint.y()))) { + + staticPoint = anchorPoint; + } + QPointF staticPointInView = m_d->transform.map(staticPoint); QPointF movingPointInView = mousePos; - if (specialModifierActive || m_d->currentArgs.keepAspectRatio()) { + if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) { KisTransformUtils::MatricesPack m(m_d->clickArgs); QTransform t = m.finalTransform(); QPointF refDiff = t.map(movingPoint) - staticPointInView; QPointF realDiff = mousePos - staticPointInView; realDiff = kisProjectOnVector(refDiff, realDiff); movingPointInView = staticPointInView + realDiff; } GSL::ScaleResult2D result = GSL::calculateScale2D(m_d->currentArgs, staticPoint, staticPointInView, movingPoint, movingPointInView); m_d->currentArgs.setScaleX(result.scaleX); m_d->currentArgs.setScaleY(result.scaleY); m_d->currentArgs.setTransformedCenter(result.transformedCenter); break; } case MOVECENTER: { QPointF pt = m_d->transform.inverted().map(mousePos); pt = KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()); QPointF newRotationCenterOffset = pt - m_d->currentArgs.originalCenter(); - if (specialModifierActive) { + if (shiftModifierActive) { if (qAbs(newRotationCenterOffset.x()) > qAbs(newRotationCenterOffset.y())) { newRotationCenterOffset.ry() = 0; } else { newRotationCenterOffset.rx() = 0; } } m_d->currentArgs.setRotationCenterOffset(newRotationCenterOffset); emit requestResetRotationCenterButtons(); } break; case TOPSHEAR: case BOTTOMSHEAR: { KisTransformUtils::MatricesPack m(m_d->clickArgs); QTransform backwardT = (m.S * m.projectedP).inverted(); QPointF diff = backwardT.map(mousePos - m_d->clickPos); qreal sign = m_d->function == BOTTOMSHEAR ? 1.0 : -1.0; // get the dx pixels corresponding to the current shearX factor qreal dx = sign * m_d->clickArgs.shearX() * m_d->clickArgs.scaleY() * m_d->transaction.originalHalfHeight(); // get the dx pixels corresponding to the current shearX factor dx += diff.x(); // calculate the new shearX factor m_d->currentArgs.setShearX(sign * dx / m_d->currentArgs.scaleY() / m_d->transaction.originalHalfHeight()); // calculate the new shearX factor break; } case LEFTSHEAR: case RIGHTSHEAR: { KisTransformUtils::MatricesPack m(m_d->clickArgs); QTransform backwardT = (m.S * m.projectedP).inverted(); QPointF diff = backwardT.map(mousePos - m_d->clickPos); qreal sign = m_d->function == RIGHTSHEAR ? 1.0 : -1.0; // get the dx pixels corresponding to the current shearX factor qreal dy = sign * m_d->clickArgs.shearY() * m_d->clickArgs.scaleX() * m_d->transaction.originalHalfWidth(); dy += diff.y(); // calculate the new shearY factor m_d->currentArgs.setShearY(sign * dy / m_d->clickArgs.scaleX() / m_d->transaction.originalHalfWidth()); break; } } m_d->recalculateTransformations(); } bool KisFreeTransformStrategy::endPrimaryAction() { bool shouldSave = !m_d->imageTooBig; if (m_d->imageTooBig) { m_d->currentArgs = m_d->clickArgs; m_d->recalculateTransformations(); } return shouldSave; } void KisFreeTransformStrategy::Private::recalculateTransformations() { KisTransformUtils::MatricesPack m(currentArgs); QTransform sanityCheckMatrix = m.TS * m.SC * m.S * m.projectedP; /** * The center of the original image should still * stay the origin of CS */ KIS_ASSERT_RECOVER_NOOP(sanityCheckMatrix.map(currentArgs.originalCenter()).manhattanLength() < 1e-4); transform = m.finalTransform(); QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); handlesTransform = transform * viewScaleTransform; QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y()); paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform; paintingOffset = transaction.originalTopLeft(); // check whether image is too big to be displayed or not imageTooBig = KisTransformUtils::checkImageTooBig(transaction.originalRect(), m); // recalculate cached handles position recalculateTransformedHandles(); emit q->requestShowImageTooBig(imageTooBig); } diff --git a/plugins/tools/tool_transform2/kis_free_transform_strategy.h b/plugins/tools/tool_transform2/kis_free_transform_strategy.h index d7ccb6d5cb..8fad40ea84 100644 --- a/plugins/tools/tool_transform2/kis_free_transform_strategy.h +++ b/plugins/tools/tool_transform2/kis_free_transform_strategy.h @@ -1,68 +1,68 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_FREE_TRANSFORM_STRATEGY_H #define __KIS_FREE_TRANSFORM_STRATEGY_H #include #include #include "kis_simplified_action_policy_strategy.h" class QPointF; class QPainter; class KisCoordinatesConverter; class ToolTransformArgs; class TransformTransactionProperties; class QCursor; class KisFreeTransformStrategy : public KisSimplifiedActionPolicyStrategy { Q_OBJECT public: KisFreeTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction); ~KisFreeTransformStrategy(); void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive); void paint(QPainter &gc); QCursor getCurrentCursor() const; void externalConfigChanged(); using KisTransformStrategyBase::beginPrimaryAction; using KisTransformStrategyBase::continuePrimaryAction; using KisTransformStrategyBase::endPrimaryAction; bool beginPrimaryAction(const QPointF &pt); - void continuePrimaryAction(const QPointF &pt, bool specialModifierActve); + void continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive); bool endPrimaryAction(); Q_SIGNALS: void requestCanvasUpdate(); void requestResetRotationCenterButtons(); void requestShowImageTooBig(bool value); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_FREE_TRANSFORM_STRATEGY_H */ diff --git a/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp index 28527d9809..aaa55684c3 100644 --- a/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp @@ -1,308 +1,309 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_liquify_transform_strategy.h" #include #include #include #include "KoPointerEvent.h" #include "kis_coordinates_converter.h" #include "tool_transform_args.h" #include "transform_transaction_properties.h" #include "krita_utils.h" #include "kis_cursor.h" #include "kis_transform_utils.h" #include "kis_algebra_2d.h" #include "kis_transform_utils.h" #include "kis_liquify_paint_helper.h" #include "kis_liquify_transform_worker.h" #include "KoCanvasResourceManager.h" struct KisLiquifyTransformStrategy::Private { Private(KisLiquifyTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction, const KoCanvasResourceManager *_manager) : manager(_manager), q(_q), converter(_converter), currentArgs(_currentArgs), transaction(_transaction), helper(_converter), recalculateOnNextRedraw(false) { } const KoCanvasResourceManager *manager; KisLiquifyTransformStrategy * const q; /// standard members /// const KisCoordinatesConverter *converter; ////// ToolTransformArgs ¤tArgs; ////// TransformTransactionProperties &transaction; QTransform paintingTransform; QPointF paintingOffset; QTransform handlesTransform; /// custom members /// QImage transformedImage; // size-gesture-related QPointF lastMouseWidgetPos; QPointF startResizeImagePos; QPoint startResizeGlobalCursorPos; KisLiquifyPaintHelper helper; bool recalculateOnNextRedraw; void recalculateTransformations(); inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization); }; KisLiquifyTransformStrategy::KisLiquifyTransformStrategy(const KisCoordinatesConverter *converter, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction, const KoCanvasResourceManager *manager) : m_d(new Private(this, converter, currentArgs, transaction, manager)) { } KisLiquifyTransformStrategy::~KisLiquifyTransformStrategy() { } QPainterPath KisLiquifyTransformStrategy::getCursorOutline() const { return m_d->helper.brushOutline(*m_d->currentArgs.liquifyProperties()); } void KisLiquifyTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) { Q_UNUSED(mousePos); Q_UNUSED(perspectiveModifierActive); } QCursor KisLiquifyTransformStrategy::getCurrentCursor() const { return Qt::BlankCursor; } void KisLiquifyTransformStrategy::paint(QPainter &gc) { // Draw preview image if (m_d->recalculateOnNextRedraw) { m_d->recalculateTransformations(); m_d->recalculateOnNextRedraw = false; } gc.save(); gc.setOpacity(m_d->transaction.basePreviewOpacity()); gc.setTransform(m_d->paintingTransform, true); gc.drawImage(m_d->paintingOffset, m_d->transformedImage); gc.restore(); } void KisLiquifyTransformStrategy::externalConfigChanged() { + if (!m_d->currentArgs.liquifyWorker()) return; m_d->recalculateTransformations(); } bool KisLiquifyTransformStrategy::acceptsClicks() const { return true; } bool KisLiquifyTransformStrategy::beginPrimaryAction(KoPointerEvent *event) { m_d->helper.configurePaintOp(*m_d->currentArgs.liquifyProperties(), m_d->currentArgs.liquifyWorker()); m_d->helper.startPaint(event, m_d->manager); m_d->recalculateTransformations(); return true; } void KisLiquifyTransformStrategy::continuePrimaryAction(KoPointerEvent *event) { m_d->helper.continuePaint(event); // the updates should be compressed m_d->recalculateOnNextRedraw = true; emit requestCanvasUpdate(); } bool KisLiquifyTransformStrategy::endPrimaryAction(KoPointerEvent *event) { if (m_d->helper.endPaint(event)) { m_d->recalculateTransformations(); emit requestCanvasUpdate(); } return true; } void KisLiquifyTransformStrategy::hoverActionCommon(KoPointerEvent *event) { m_d->helper.hoverPaint(event); } void KisLiquifyTransformStrategy::activateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties(); props->setReverseDirection(!props->reverseDirection()); emit requestUpdateOptionWidget(); } } void KisLiquifyTransformStrategy::deactivateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties(); props->setReverseDirection(!props->reverseDirection()); emit requestUpdateOptionWidget(); } } bool KisLiquifyTransformStrategy::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (action == KisTool::ChangeSize) { QPointF widgetPoint = m_d->converter->documentToWidget(event->point); m_d->lastMouseWidgetPos = widgetPoint; m_d->startResizeImagePos = m_d->converter->documentToImage(event->point); m_d->startResizeGlobalCursorPos = QCursor::pos(); return true; } else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { return beginPrimaryAction(event); } return false; } void KisLiquifyTransformStrategy::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (action == KisTool::ChangeSize) { QPointF widgetPoint = m_d->converter->documentToWidget(event->point); QPointF diff = widgetPoint - m_d->lastMouseWidgetPos; KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties(); const qreal linearizedOffset = diff.x() / KisTransformUtils::scaleFromAffineMatrix(m_d->converter->imageToWidgetTransform()); const qreal newSize = qBound(props->minSize(), props->size() + linearizedOffset, props->maxSize()); props->setSize(newSize); m_d->currentArgs.saveLiquifyTransformMode(); m_d->lastMouseWidgetPos = widgetPoint; emit requestCursorOutlineUpdate(m_d->startResizeImagePos); } else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { return continuePrimaryAction(event); } } bool KisLiquifyTransformStrategy::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(event); if (action == KisTool::ChangeSize) { QCursor::setPos(m_d->startResizeGlobalCursorPos); return true; } else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { return endPrimaryAction(event); } return false; } inline QPointF KisLiquifyTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization) { return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt); } void KisLiquifyTransformStrategy::Private::recalculateTransformations() { KIS_ASSERT_RECOVER_RETURN(currentArgs.liquifyWorker()); QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter); QTransform resultTransform = q->thumbToImageTransform() * scaleTransform; qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultTransform); bool useFlakeOptimization = scale < 1.0; paintingOffset = transaction.originalTopLeft(); if (!q->originalImage().isNull()) { if (useFlakeOptimization) { transformedImage = q->originalImage().transformed(q->thumbToImageTransform() * scaleTransform); paintingTransform = QTransform(); } else { transformedImage = q->originalImage(); paintingTransform = q->thumbToImageTransform() * scaleTransform; } QTransform imageToRealThumbTransform = useFlakeOptimization ? scaleTransform : QTransform(); QPointF origTLInFlake = imageToRealThumbTransform.map(transaction.originalTopLeft()); transformedImage = currentArgs.liquifyWorker()->runOnQImage(transformedImage, origTLInFlake, imageToRealThumbTransform, &paintingOffset); } else { transformedImage = q->originalImage(); paintingOffset = imageToThumb(transaction.originalTopLeft(), false); paintingTransform = q->thumbToImageTransform() * scaleTransform; } handlesTransform = scaleTransform; } diff --git a/plugins/tools/tool_transform2/kis_perspective_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_perspective_transform_strategy.cpp index 850e098e15..9c23c4d1e3 100644 --- a/plugins/tools/tool_transform2/kis_perspective_transform_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_perspective_transform_strategy.cpp @@ -1,669 +1,683 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_perspective_transform_strategy.h" #include #include #include #include #include #include "kis_coordinates_converter.h" #include "tool_transform_args.h" #include "transform_transaction_properties.h" #include "krita_utils.h" #include "kis_cursor.h" #include "kis_transform_utils.h" #include "kis_free_transform_strategy_gsl_helpers.h" enum StrokeFunction { DRAG_HANDLE = 0, DRAG_X_VANISHING_POINT, DRAG_Y_VANISHING_POINT, MOVE, NONE }; struct KisPerspectiveTransformStrategy::Private { Private(KisPerspectiveTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction) : q(_q), converter(_converter), currentArgs(_currentArgs), transaction(_transaction), imageTooBig(false) { } KisPerspectiveTransformStrategy *q; /// standard members /// const KisCoordinatesConverter *converter; ////// ToolTransformArgs ¤tArgs; ////// TransformTransactionProperties &transaction; QTransform thumbToImageTransform; QImage originalImage; QTransform paintingTransform; QPointF paintingOffset; QTransform handlesTransform; /// custom members /// StrokeFunction function; struct HandlePoints { bool xVanishingExists; bool yVanishingExists; QPointF xVanishing; QPointF yVanishing; }; HandlePoints transformedHandles; QTransform transform; QVector srcCornerPoints; QVector dstCornerPoints; int currentDraggingCornerPoint; bool imageTooBig; QPointF clickPos; ToolTransformArgs clickArgs; QCursor getScaleCursor(const QPointF &handlePt); QCursor getShearCursor(const QPointF &start, const QPointF &end); void recalculateTransformations(); void recalculateTransformedHandles(); void transformIntoArgs(const Eigen::Matrix3f &t); QTransform transformFromArgs(); }; KisPerspectiveTransformStrategy::KisPerspectiveTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction) : KisSimplifiedActionPolicyStrategy(converter, snapGuide), m_d(new Private(this, converter, currentArgs, transaction)) { } KisPerspectiveTransformStrategy::~KisPerspectiveTransformStrategy() { } void KisPerspectiveTransformStrategy::Private::recalculateTransformedHandles() { srcCornerPoints.clear(); srcCornerPoints << transaction.originalTopLeft(); srcCornerPoints << transaction.originalTopRight(); srcCornerPoints << transaction.originalBottomLeft(); srcCornerPoints << transaction.originalBottomRight(); dstCornerPoints.clear(); Q_FOREACH (const QPointF &pt, srcCornerPoints) { dstCornerPoints << transform.map(pt); } QMatrix4x4 realMatrix(transform); QVector4D v; v = QVector4D(1, 0, 0, 0); v = realMatrix * v; transformedHandles.xVanishingExists = !qFuzzyCompare(v.w(), 0); transformedHandles.xVanishing = v.toVector2DAffine().toPointF(); v = QVector4D(0, 1, 0, 0); v = realMatrix * v; transformedHandles.yVanishingExists = !qFuzzyCompare(v.w(), 0); transformedHandles.yVanishing = v.toVector2DAffine().toPointF(); } void KisPerspectiveTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) { Q_UNUSED(perspectiveModifierActive); QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect())); StrokeFunction defaultFunction = transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : NONE; KisTransformUtils::HandleChooser handleChooser(mousePos, defaultFunction); qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter); if (!m_d->transformedHandles.xVanishing.isNull()) { handleChooser.addFunction(m_d->transformedHandles.xVanishing, handleRadius, DRAG_X_VANISHING_POINT); } if (!m_d->transformedHandles.yVanishing.isNull()) { handleChooser.addFunction(m_d->transformedHandles.yVanishing, handleRadius, DRAG_Y_VANISHING_POINT); } m_d->currentDraggingCornerPoint = -1; for (int i = 0; i < m_d->dstCornerPoints.size(); i++) { if (handleChooser.addFunction(m_d->dstCornerPoints[i], handleRadius, DRAG_HANDLE)) { m_d->currentDraggingCornerPoint = i; } } m_d->function = handleChooser.function(); } QCursor KisPerspectiveTransformStrategy::getCurrentCursor() const { QCursor cursor; switch (m_d->function) { case NONE: cursor = KisCursor::arrowCursor(); break; case MOVE: cursor = KisCursor::moveCursor(); break; case DRAG_HANDLE: case DRAG_X_VANISHING_POINT: case DRAG_Y_VANISHING_POINT: cursor = KisCursor::pointingHandCursor(); break; } return cursor; } void KisPerspectiveTransformStrategy::paint(QPainter &gc) { gc.save(); gc.setOpacity(m_d->transaction.basePreviewOpacity()); gc.setTransform(m_d->paintingTransform, true); gc.drawImage(m_d->paintingOffset, originalImage()); gc.restore(); // Draw Handles QPainterPath handles; handles.moveTo(m_d->transaction.originalTopLeft()); handles.lineTo(m_d->transaction.originalTopRight()); handles.lineTo(m_d->transaction.originalBottomRight()); handles.lineTo(m_d->transaction.originalBottomLeft()); handles.lineTo(m_d->transaction.originalTopLeft()); auto addHandleRectFunc = [&](const QPointF &pt) { handles.addRect( KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius, m_d->handlesTransform, m_d->transaction.originalRect(), pt) .translated(pt)); }; addHandleRectFunc(m_d->transaction.originalTopLeft()); addHandleRectFunc(m_d->transaction.originalTopRight()); addHandleRectFunc(m_d->transaction.originalBottomLeft()); addHandleRectFunc(m_d->transaction.originalBottomRight()); gc.save(); /** * WARNING: we cannot install a transform to paint the handles here! * * There is a bug in Qt that prevents painting of cosmetic-pen * brushes in openGL mode when a TxProject matrix is active on * a QPainter. So just convert it manually. * * https://bugreports.qt-project.org/browse/QTBUG-42658 */ //gc.setTransform(m_d->handlesTransform, true); <-- don't do like this! QPainterPath mappedHandles = m_d->handlesTransform.map(handles); QPen pen[2]; pen[0].setWidth(1); pen[1].setWidth(2); pen[1].setColor(Qt::lightGray); for (int i = 1; i >= 0; --i) { gc.setPen(pen[i]); gc.drawPath(mappedHandles); } gc.restore(); { // painting perspective handles QPainterPath perspectiveHandles; QRectF handleRect = KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius, QTransform(), m_d->transaction.originalRect(), 0, 0); if (m_d->transformedHandles.xVanishingExists) { QRectF rc = handleRect.translated(m_d->transformedHandles.xVanishing); perspectiveHandles.addEllipse(rc); } if (m_d->transformedHandles.yVanishingExists) { QRectF rc = handleRect.translated(m_d->transformedHandles.yVanishing); perspectiveHandles.addEllipse(rc); } if (!perspectiveHandles.isEmpty()) { gc.save(); gc.setTransform(m_d->converter->imageToWidgetTransform()); gc.setBrush(Qt::red); for (int i = 1; i >= 0; --i) { gc.setPen(pen[i]); gc.drawPath(perspectiveHandles); } gc.restore(); } } } void KisPerspectiveTransformStrategy::externalConfigChanged() { m_d->recalculateTransformations(); } bool KisPerspectiveTransformStrategy::beginPrimaryAction(const QPointF &pt) { Q_UNUSED(pt); if (m_d->function == NONE) return false; m_d->clickPos = pt; m_d->clickArgs = m_d->currentArgs; return true; } Eigen::Matrix3f getTransitionMatrix(const QVector &sp) { Eigen::Matrix3f A; Eigen::Vector3f v3; A << sp[0].x() , sp[1].x() , sp[2].x() ,sp[0].y() , sp[1].y() , sp[2].y() , 1 , 1 , 1; v3 << sp[3].x() , sp[3].y() , 1; Eigen::Vector3f coeffs = A.colPivHouseholderQr().solve(v3); A.col(0) *= coeffs(0); A.col(1) *= coeffs(1); A.col(2) *= coeffs(2); return A; } QTransform toQTransform(const Eigen::Matrix3f &m) { return QTransform(m(0,0), m(1,0), m(2,0), m(0,1), m(1,1), m(2,1), m(0,2), m(1,2), m(2,2)); } Eigen::Matrix3f fromQTransform(const QTransform &t) { Eigen::Matrix3f m; m << t.m11() , t.m21() , t.m31() ,t.m12() , t.m22() , t.m32() ,t.m13() , t.m23() , t.m33(); return m; } Eigen::Matrix3f fromTranslate(const QPointF &pt) { Eigen::Matrix3f m; m << 1 , 0 , pt.x() ,0 , 1 , pt.y() ,0 , 0 , 1; return m; } Eigen::Matrix3f fromScale(qreal sx, qreal sy) { Eigen::Matrix3f m; m << sx , 0 , 0 ,0 , sy , 0 ,0 , 0 , 1; return m; } Eigen::Matrix3f fromShear(qreal sx, qreal sy) { Eigen::Matrix3f m; m << 1 , sx , 0 ,sy , sx*sy + 1, 0 ,0 , 0 , 1; return m; } void KisPerspectiveTransformStrategy::Private::transformIntoArgs(const Eigen::Matrix3f &t) { Eigen::Matrix3f TS = fromTranslate(-currentArgs.originalCenter()); Eigen::Matrix3f m = t * TS.inverse(); qreal tX = m(0,2) / m(2,2); qreal tY = m(1,2) / m(2,2); Eigen::Matrix3f T = fromTranslate(QPointF(tX, tY)); m = T.inverse() * m; - const qreal factor = (m(1,1) / m(0,1) - m(1,0) / m(0,0)); + // TODO: implement matrix decomposition as described here + // https://www.w3.org/TR/css-transforms-1/#decomposing-a-3d-matrix - qreal scaleX = m(0,0) / m(2,2); - qreal scaleY = m(0,1) / m(2,2) * factor; + // For now use an extremely hackish approximation + if (m(0,1) != 0.0 && m(0,0) != 0.0 && m(2,2) != 0.0) { - Eigen::Matrix3f SC = fromScale(scaleX, scaleY); + const qreal factor = (m(1,1) / m(0,1) - m(1,0) / m(0,0)); - qreal shearX = 1.0 / factor; - qreal shearY = m(1,0) / m(0,0); + qreal scaleX = m(0,0) / m(2,2); + qreal scaleY = m(0,1) / m(2,2) * factor; - Eigen::Matrix3f S = fromShear(shearX, shearY); + Eigen::Matrix3f SC = fromScale(scaleX, scaleY); - currentArgs.setScaleX(scaleX); - currentArgs.setScaleY(scaleY); + qreal shearX = 1.0 / factor; + qreal shearY = m(1,0) / m(0,0); - currentArgs.setShearX(shearX); - currentArgs.setShearY(shearY); + Eigen::Matrix3f S = fromShear(shearX, shearY); - currentArgs.setTransformedCenter(QPointF(tX, tY)); + currentArgs.setScaleX(scaleX); + currentArgs.setScaleY(scaleY); + + currentArgs.setShearX(shearX); + currentArgs.setShearY(shearY); + + m = m * SC.inverse(); + m = m * S.inverse(); + m /= m(2,2); + } else { + currentArgs.setScaleX(1.0); + currentArgs.setScaleY(1.0); - m = m * SC.inverse(); - m = m * S.inverse(); - m /= m(2,2); + currentArgs.setShearX(0.0); + currentArgs.setShearY(0.0); + } + + currentArgs.setTransformedCenter(QPointF(tX, tY)); currentArgs.setFlattenedPerspectiveTransform(toQTransform(m)); } QTransform KisPerspectiveTransformStrategy::Private::transformFromArgs() { KisTransformUtils::MatricesPack m(currentArgs); return m.finalTransform(); } QVector4D fromQPointF(const QPointF &pt) { return QVector4D(pt.x(), pt.y(), 0, 1.0); } QPointF toQPointF(const QVector4D &v) { return v.toVector2DAffine().toPointF(); } -void KisPerspectiveTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool specialModifierActve) +void KisPerspectiveTransformStrategy::continuePrimaryAction(const QPointF &mousePos, bool shiftModifierActve, bool altModifierActive) { - Q_UNUSED(specialModifierActve); + Q_UNUSED(shiftModifierActve); + Q_UNUSED(altModifierActive); switch (m_d->function) { case NONE: break; case MOVE: { QPointF diff = mousePos - m_d->clickPos; m_d->currentArgs.setTransformedCenter( m_d->clickArgs.transformedCenter() + diff); break; } case DRAG_HANDLE: { KIS_ASSERT_RECOVER_RETURN(m_d->currentDraggingCornerPoint >=0); m_d->dstCornerPoints[m_d->currentDraggingCornerPoint] = mousePos; Eigen::Matrix3f A = getTransitionMatrix(m_d->srcCornerPoints); Eigen::Matrix3f B = getTransitionMatrix(m_d->dstCornerPoints); Eigen::Matrix3f result = B * A.inverse(); m_d->transformIntoArgs(result); break; } case DRAG_X_VANISHING_POINT: case DRAG_Y_VANISHING_POINT: { QMatrix4x4 m(m_d->transform); QPointF tl = m_d->transaction.originalTopLeft(); QPointF tr = m_d->transaction.originalTopRight(); QPointF bl = m_d->transaction.originalBottomLeft(); QPointF br = m_d->transaction.originalBottomRight(); QVector4D v(1,0,0,0); QVector4D otherV(0,1,0,0); if (m_d->function == DRAG_X_VANISHING_POINT) { v = QVector4D(1,0,0,0); otherV = QVector4D(0,1,0,0); } else { v = QVector4D(0,1,0,0); otherV = QVector4D(1,0,0,0); } QPointF tl_dst = toQPointF(m * fromQPointF(tl)); QPointF tr_dst = toQPointF(m * fromQPointF(tr)); QPointF bl_dst = toQPointF(m * fromQPointF(bl)); QPointF br_dst = toQPointF(m * fromQPointF(br)); QPointF v_dst = toQPointF(m * v); QPointF otherV_dst = toQPointF(m * otherV); QVector srcPoints; QVector dstPoints; QPointF far1_src; QPointF far2_src; QPointF near1_src; QPointF near2_src; QPointF far1_dst; QPointF far2_dst; QPointF near1_dst; QPointF near2_dst; if (m_d->function == DRAG_X_VANISHING_POINT) { // topLeft (far) --- topRight (near) --- vanishing if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, tr_dst)) { far1_src = tl; far2_src = bl; near1_src = tr; near2_src = br; far1_dst = tl_dst; far2_dst = bl_dst; near1_dst = tr_dst; near2_dst = br_dst; // topRight (far) --- topLeft (near) --- vanishing } else { far1_src = tr; far2_src = br; near1_src = tl; near2_src = bl; far1_dst = tr_dst; far2_dst = br_dst; near1_dst = tl_dst; near2_dst = bl_dst; } } else /* if (m_d->function == DRAG_Y_VANISHING_POINT) */{ // topLeft (far) --- bottomLeft (near) --- vanishing if (kisSquareDistance(v_dst, tl_dst) > kisSquareDistance(v_dst, bl_dst)) { far1_src = tl; far2_src = tr; near1_src = bl; near2_src = br; far1_dst = tl_dst; far2_dst = tr_dst; near1_dst = bl_dst; near2_dst = br_dst; // bottomLeft (far) --- topLeft (near) --- vanishing } else { far1_src = bl; far2_src = br; near1_src = tl; near2_src = tr; far1_dst = bl_dst; far2_dst = br_dst; near1_dst = tl_dst; near2_dst = tr_dst; } } QLineF l0(far1_dst, mousePos); QLineF l1(far2_dst, mousePos); QLineF l2(otherV_dst, near1_dst); l0.intersect(l2, &near1_dst); l1.intersect(l2, &near2_dst); srcPoints << far1_src; srcPoints << far2_src; srcPoints << near1_src; srcPoints << near2_src; dstPoints << far1_dst; dstPoints << far2_dst; dstPoints << near1_dst; dstPoints << near2_dst; Eigen::Matrix3f A = getTransitionMatrix(srcPoints); Eigen::Matrix3f B = getTransitionMatrix(dstPoints); Eigen::Matrix3f result = B * A.inverse(); m_d->transformIntoArgs(result); break; } } m_d->recalculateTransformations(); } bool KisPerspectiveTransformStrategy::endPrimaryAction() { bool shouldSave = !m_d->imageTooBig; if (m_d->imageTooBig) { m_d->currentArgs = m_d->clickArgs; m_d->recalculateTransformations(); } return shouldSave; } void KisPerspectiveTransformStrategy::Private::recalculateTransformations() { transform = transformFromArgs(); QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); handlesTransform = transform * viewScaleTransform; QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y()); paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform; paintingOffset = transaction.originalTopLeft(); // check whether image is too big to be displayed or not const qreal maxScale = 20.0; imageTooBig = false; if (qAbs(currentArgs.scaleX()) > maxScale || qAbs(currentArgs.scaleY()) > maxScale) { imageTooBig = true; } else { QVector points; points << transaction.originalRect().topLeft(); points << transaction.originalRect().topRight(); points << transaction.originalRect().bottomRight(); points << transaction.originalRect().bottomLeft(); for (int i = 0; i < points.size(); i++) { points[i] = transform.map(points[i]); } for (int i = 0; i < points.size(); i++) { const QPointF &pt = points[i]; const QPointF &prev = points[(i - 1 + 4) % 4]; const QPointF &next = points[(i + 1) % 4]; const QPointF &other = points[(i + 2) % 4]; QLineF l1(pt, other); QLineF l2(prev, next); QPointF intersection; l1.intersect(l2, &intersection); qreal maxDistance = kisSquareDistance(pt, other); if (kisSquareDistance(pt, intersection) > maxDistance || kisSquareDistance(other, intersection) > maxDistance) { imageTooBig = true; break; } const qreal thresholdDistance = 0.02 * l2.length(); if (kisDistanceToLine(pt, l2) < thresholdDistance) { imageTooBig = true; break; } } } // recalculate cached handles position recalculateTransformedHandles(); emit q->requestShowImageTooBig(imageTooBig); } diff --git a/plugins/tools/tool_transform2/kis_perspective_transform_strategy.h b/plugins/tools/tool_transform2/kis_perspective_transform_strategy.h index a5af0daf2b..2f8f10d50e 100644 --- a/plugins/tools/tool_transform2/kis_perspective_transform_strategy.h +++ b/plugins/tools/tool_transform2/kis_perspective_transform_strategy.h @@ -1,67 +1,67 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_PERSPECTIVE_TRANSFORM_STRATEGY_H #define __KIS_PERSPECTIVE_TRANSFORM_STRATEGY_H #include #include #include "kis_simplified_action_policy_strategy.h" class QPointF; class QPainter; class KisCoordinatesConverter; class ToolTransformArgs; class TransformTransactionProperties; class QCursor; class KisPerspectiveTransformStrategy : public KisSimplifiedActionPolicyStrategy { Q_OBJECT public: KisPerspectiveTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction); ~KisPerspectiveTransformStrategy(); void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive); void paint(QPainter &gc); QCursor getCurrentCursor() const; void externalConfigChanged(); using KisTransformStrategyBase::beginPrimaryAction; using KisTransformStrategyBase::continuePrimaryAction; using KisTransformStrategyBase::endPrimaryAction; bool beginPrimaryAction(const QPointF &pt); - void continuePrimaryAction(const QPointF &pt, bool specialModifierActve); + void continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive); bool endPrimaryAction(); Q_SIGNALS: void requestCanvasUpdate(); void requestShowImageTooBig(bool value); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_PERSPECTIVE_TRANSFORM_STRATEGY_H */ diff --git a/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.cpp b/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.cpp index 9315cbeeb7..7134cc6a2c 100644 --- a/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.cpp @@ -1,171 +1,200 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_simplified_action_policy_strategy.h" #include "KoPointerEvent.h" #include #include "kis_coordinates_converter.h" struct KisSimplifiedActionPolicyStrategy::Private { Private(const KisCoordinatesConverter *_converter, KoSnapGuide *_snapGuide) : converter(_converter), snapGuide(_snapGuide), changeSizeModifierActive(false), anyPickerModifierActive(false) {} const KisCoordinatesConverter *converter; KoSnapGuide *snapGuide; bool changeSizeModifierActive; bool anyPickerModifierActive; QPointF dragOffset; + QPointF lastImagePos; }; KisSimplifiedActionPolicyStrategy::KisSimplifiedActionPolicyStrategy(const KisCoordinatesConverter *_converter, KoSnapGuide *_snapGuide) : m_d(new Private(_converter, _snapGuide)) { } KisSimplifiedActionPolicyStrategy::~KisSimplifiedActionPolicyStrategy() { } QPointF KisSimplifiedActionPolicyStrategy::handleSnapPoint(const QPointF &imagePos) { return imagePos; } bool KisSimplifiedActionPolicyStrategy::beginPrimaryAction(KoPointerEvent *event) { const QPointF rawImagePoint = m_d->converter->documentToImage(event->point); const QPointF snappedImagePoint = handleSnapPoint(rawImagePoint); /** * Note: Snapping with Offset is not yet used in the transform * strategies. When the user starts an action, we just move * the handle to the mouse position, even if it was * positioned with an offset. That is not what we do in * Crop Tool. */ if (m_d->snapGuide && rawImagePoint != snappedImagePoint) { QPointF imageOffset = snappedImagePoint - rawImagePoint; m_d->dragOffset = m_d->converter->imageToDocument(imageOffset); } const QPointF pos = m_d->snapGuide ? m_d->snapGuide->snap(event->point, m_d->dragOffset, event->modifiers()) : event->point; - return beginPrimaryAction(m_d->converter->documentToImage(pos)); + QPointF imagePos = m_d->converter->documentToImage(pos); + m_d->lastImagePos = imagePos; + + return beginPrimaryAction(imagePos); } void KisSimplifiedActionPolicyStrategy::continuePrimaryAction(KoPointerEvent *event) { /** * HACK ALERT! * * Here we explicitly check for Shift key pressed! The chioce of * the stroke type is usually done before the tablet press, but * for some actions like constrain proportions we should be able * to activate it even after the strokehas been started. For now, * KisShortcutMatcher does not support it, so just hardcode this * special case. * * See bug 340496 */ const bool shiftIsActive = event->modifiers() & Qt::ShiftModifier; + const bool altIsActive = event->modifiers() & Qt::AltModifier; const QPointF pos = m_d->snapGuide ? m_d->snapGuide->snap(event->point, m_d->dragOffset, event->modifiers()) : event->point; - return continuePrimaryAction(m_d->converter->documentToImage(pos), shiftIsActive); + QPointF imagePos = m_d->converter->documentToImage(pos); + m_d->lastImagePos = imagePos; + + return continuePrimaryAction(imagePos, shiftIsActive, altIsActive); } void KisSimplifiedActionPolicyStrategy::hoverActionCommon(KoPointerEvent *event) { - hoverActionCommon(m_d->converter->documentToImage(event->point)); + QPointF imagePos = m_d->converter->documentToImage(event->point); + m_d->lastImagePos = imagePos; + + hoverActionCommon(imagePos); } bool KisSimplifiedActionPolicyStrategy::endPrimaryAction(KoPointerEvent *event) { - Q_UNUSED(event); + QPointF imagePos = m_d->converter->documentToImage(event->point); + m_d->lastImagePos = imagePos; + return endPrimaryAction(); } +void KisSimplifiedActionPolicyStrategy::activatePrimaryAction() +{ + setTransformFunction(m_d->lastImagePos, m_d->anyPickerModifierActive); +} + void KisSimplifiedActionPolicyStrategy::activateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::ChangeSize) { m_d->changeSizeModifierActive = true; } else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { m_d->anyPickerModifierActive = true; } + + setTransformFunction(m_d->lastImagePos, m_d->anyPickerModifierActive); } void KisSimplifiedActionPolicyStrategy::deactivateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::ChangeSize) { m_d->changeSizeModifierActive = false; } else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode || action == KisTool::PickFgImage || action == KisTool::PickBgImage) { m_d->anyPickerModifierActive = false; } } bool KisSimplifiedActionPolicyStrategy::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); if (!m_d->changeSizeModifierActive && !m_d->anyPickerModifierActive) return false; - return beginPrimaryAction(m_d->converter->documentToImage(event->point)); + QPointF imagePos = m_d->converter->documentToImage(event->point); + m_d->lastImagePos = imagePos; + + return beginPrimaryAction(imagePos); } void KisSimplifiedActionPolicyStrategy::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(action); if (!m_d->changeSizeModifierActive && !m_d->anyPickerModifierActive) return; + const bool altIsActive = event->modifiers() & Qt::AltModifier; - continuePrimaryAction(m_d->converter->documentToImage(event->point), m_d->changeSizeModifierActive); + QPointF imagePos = m_d->converter->documentToImage(event->point); + m_d->lastImagePos = imagePos; + + continuePrimaryAction(imagePos, m_d->changeSizeModifierActive, altIsActive); } bool KisSimplifiedActionPolicyStrategy::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { - Q_UNUSED(event); Q_UNUSED(action); if (!m_d->changeSizeModifierActive && !m_d->anyPickerModifierActive) return false; + QPointF imagePos = m_d->converter->documentToImage(event->point); + m_d->lastImagePos = imagePos; + return endPrimaryAction(); } void KisSimplifiedActionPolicyStrategy::hoverActionCommon(const QPointF &pt) { setTransformFunction(pt, m_d->anyPickerModifierActive); } diff --git a/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.h b/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.h index d3914c77dc..3edc20398d 100644 --- a/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.h +++ b/plugins/tools/tool_transform2/kis_simplified_action_policy_strategy.h @@ -1,65 +1,67 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_SIMPLIFIED_ACTION_POLICY_STRATEGY_H #define __KIS_SIMPLIFIED_ACTION_POLICY_STRATEGY_H #include #include "kis_transform_strategy_base.h" class KoPointerEvent; class KisCoordinatesConverter; class KoSnapGuide; class KisSimplifiedActionPolicyStrategy : public KisTransformStrategyBase { public: KisSimplifiedActionPolicyStrategy(const KisCoordinatesConverter *_converter, KoSnapGuide *snapGuide = 0); ~KisSimplifiedActionPolicyStrategy(); + void activatePrimaryAction(); + virtual bool beginPrimaryAction(KoPointerEvent *event); virtual void continuePrimaryAction(KoPointerEvent *event); virtual bool endPrimaryAction(KoPointerEvent *event); virtual void hoverActionCommon(KoPointerEvent *event); virtual QPointF handleSnapPoint(const QPointF &imagePos); virtual void activateAlternateAction(KisTool::AlternateAction action); virtual void deactivateAlternateAction(KisTool::AlternateAction action); virtual bool beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); virtual bool endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); protected: virtual void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) = 0; virtual bool beginPrimaryAction(const QPointF &pt) = 0; - virtual void continuePrimaryAction(const QPointF &pt, bool specialModifierActve) = 0; + virtual void continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive) = 0; virtual bool endPrimaryAction() = 0; virtual void hoverActionCommon(const QPointF &pt); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_SIMPLIFIED_ACTION_POLICY_STRATEGY_H */ diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index ae5fec7812..60b113cf79 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1178 +1,1194 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.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 "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_changesTracker(&m_transaction) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged()), this, SLOT(slotTrackerChangedConfig())); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { - KisTool::resetCursorStyle(); - - overrideCursorIfNotEditable(); + setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeData.strokeId()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } gc.save(); if (m_optionsWidget && m_optionsWidget->showDecorations()) { gc.setOpacity(0.3); gc.fillPath(m_selectionPath, Qt::black); } gc.restore(); currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeData.strokeId()) { useCursor(KisCursor::pointingHandCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } + if (currentNode()->inherits("KisShapeLayer") || currentNode()->inherits("KisFileLayer")) { + QString message = i18n("The transform tool cannot transform a vector or file layer. Use a transform mask instead."); + KisCanvas2 * kiscanvas = static_cast(canvas()); + kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); + event->ignore(); + return; + } + if (!m_strokeData.strokeId()) { startStroke(m_currentArgs.mode(), false); } else { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } +void KisToolTransform::activatePrimaryAction() +{ + currentStrategy()->activatePrimaryAction(); + setFunctionalCursor(); +} + +void KisToolTransform::deactivatePrimaryAction() +{ + currentStrategy()->deactivatePrimaryAction(); +} + void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); + setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (!MOVE_CONDITION(event, KisTool::PAINT_MODE)) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::touchEvent( QTouchEvent* event ) { //Count all moving touch points int touchCount = 0; Q_FOREACH ( QTouchEvent::TouchPoint tp, event->touchPoints() ) { if( tp.state() == Qt::TouchPointMoved ) { touchCount++; } } //Use the touch point count to determine the gesture switch( touchCount ) { case 1: { //Panning QTouchEvent::TouchPoint tp = event->touchPoints().at( 0 ); QPointF diff = tp.screenPos() - tp.lastScreenPos(); m_currentArgs.setTransformedCenter( m_currentArgs.transformedCenter() + diff ); outlineChanged(); break; } case 2: { //Scaling QTouchEvent::TouchPoint tp1 = event->touchPoints().at( 0 ); QTouchEvent::TouchPoint tp2 = event->touchPoints().at( 1 ); float lastZoom = (tp1.lastScreenPos() - tp2.lastScreenPos()).manhattanLength(); float newZoom = (tp1.screenPos() - tp2.screenPos()).manhattanLength(); float diff = (newZoom - lastZoom) / 100; m_currentArgs.setScaleX( m_currentArgs.scaleX() + diff ); m_currentArgs.setScaleY( m_currentArgs.scaleY() + diff ); outlineChanged(); break; } case 3: { //Rotation /* TODO: implement touch-based rotation. Vector2f center; Q_FOREACH ( const QTouchEvent::TouchPoint &tp, event->touchPoints() ) { if( tp.state() == Qt::TouchPointMoved ) { center += Vector2f( tp.screenPos().x(), tp.screenPos().y() ); } } center /= touchCount; QTouchEvent::TouchPoint tp = event->touchPoints().at(0); Vector2f oldPosition = (Vector2f( tp.lastScreenPos().x(), tp.lastScreenPos().y() ) - center).normalized(); Vector2f newPosition = (Vector2f( tp.screenPos().x(), tp.screenPos().y() ) - center).normalized(); float oldAngle = qAcos( oldPosition.dot( Vector2f( 0.0f, 0.0f ) ) ); float newAngle = qAcos( newPosition.dot( Vector2f( 0.0f, 0.0f ) ) ); float diff = newAngle - oldAngle; m_currentArgs.setAZ( m_currentArgs.aZ() + diff ); outlineChanged(); */ break; } } } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { m_currentArgs = adapter->transformArgs(); initGuiAfterTransformMode(); result = true; } } return result; } bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode) { bool result = false; const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand(); KisNodeSP oldRootNode; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode) && args->mode() == mode && oldRootNode == currentNode) { args->saveContinuedState(); image()->undoAdapter()->undoLastCommand(); // FIXME: can we make it async? image()->waitForDone(); result = true; } return result; } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { // NOTE: we are requesting an old value of m_currentArgs variable // here, which is global, don't forget about this on higher // levels. QString filterId = m_currentArgs.filterId(); m_currentArgs = ToolTransformArgs(); m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric()); m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric()); if (mode == ToolTransformArgs::FREE_TRANSFORM) { m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { m_currentArgs.setMode(ToolTransformArgs::WARP); m_optionsWidget->setDefaultWarpPoints(); m_currentArgs.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { m_currentArgs.setMode(ToolTransformArgs::CAGE); m_currentArgs.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { m_currentArgs.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = m_transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); } void KisToolTransform::updateSelectionPath() { m_selectionPath = QPainterPath(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); QPainterPath selectionOutline; KisSelectionSP selection = resources->activeSelection(); if (selection && selection->outlineCacheValid()) { selectionOutline = selection->outlineCache(); } else { selectionOutline.addRect(m_selectedPortionCache->exactBounds()); } const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); m_selectionPath = i2f.map(selectionOutline); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, currentNode()); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeData.strokeId()) return; m_changesTracker.requestUndo(); } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { cancelStroke(); } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeData.strokeId()); - KisPaintDeviceSP dev; - KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) { return; } /** * FIXME: The transform tool is not completely asynchronous, it * needs the content of the layer for creation of the stroke * strategy. It means that we cannot start a new stroke until the * previous one is finished. Ideally, we should create the * m_selectedPortionCache and m_selectionPath somewhere in the * stroke and pass it to the tool somehow. But currently, we will * just disable starting a new stroke asynchronously */ if (image()->tryBarrierLock()) { image()->unlock(); } else { return; } ToolTransformArgs fetchedArgs; bool fetchedFromCommand = false; if (!forceReset) { fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode); } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, resources->activeSelection(), image().data()); KisPaintDeviceSP previewDevice = strategy->previewDevice(); KisSelectionSP selection = strategy->realSelection(); QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds(); if (!selection && resources->activeSelection()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); } if (srcRect.isEmpty()) { delete strategy; KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); return; } m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode); initThumbnailImage(previewDevice); updateSelectionPath(); if (!forceReset && fetchedFromCommand) { m_currentArgs = fetchedArgs; initGuiAfterTransformMode(); } else if (forceReset || !tryInitTransformModeFromNode(currentNode)) { initTransformMode(mode); } m_strokeData = StrokeData(image()->startStroke(strategy)); bool haveInvisibleNodes = clearDevices(m_transaction.rootNode(), m_workRecursively); if (haveInvisibleNodes) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } Q_ASSERT(m_changesTracker.isEmpty()); commitChanges(); } void KisToolTransform::endStroke() { if (!m_strokeData.strokeId()) return; if (!m_currentArgs.isIdentity()) { transformDevices(m_transaction.rootNode(), m_workRecursively); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::SELECTION, m_currentArgs, m_transaction.rootNode())); image()->endStroke(m_strokeData.strokeId()); } else { image()->cancelStroke(m_strokeData.strokeId()); } m_strokeData.clear(); m_changesTracker.reset(); } void KisToolTransform::cancelStroke() { if (!m_strokeData.strokeId()) return; if (m_currentArgs.continuedTransform()) { m_currentArgs.restoreContinuedState(); endStroke(); } else { image()->cancelStroke(m_strokeData.strokeId()); m_strokeData.clear(); m_changesTracker.reset(); } } void KisToolTransform::commitChanges() { if (!m_strokeData.strokeId()) return; m_changesTracker.commitConfig(m_currentArgs); } void KisToolTransform::slotTrackerChangedConfig() { slotUiChangedConfig(); updateOptionWidget(); } bool KisToolTransform::clearDevices(KisNodeSP node, bool recursive) { bool haveInvisibleNodes = false; if (!node->isEditable(false)) return haveInvisibleNodes; haveInvisibleNodes = !node->visible(false); if (recursive) { // simple tail-recursive iteration KisNodeSP prevNode = node->lastChild(); while(prevNode) { haveInvisibleNodes |= clearDevices(prevNode, recursive); prevNode = prevNode->prevSibling(); } } image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::ClearSelectionData(node)); /** * It might happen that the editablity state of the node would * change during the stroke, so we need to save the set of * applicable nodes right in the beginning of the processing */ m_strokeData.addClearedNode(node); return haveInvisibleNodes; } void KisToolTransform::transformDevices(KisNodeSP node, bool recursive) { if (!node->isEditable()) return; KIS_ASSERT_RECOVER_RETURN(recursive || (m_strokeData.clearedNodes().size() == 1 && KisNodeSP(m_strokeData.clearedNodes().first()) == node)); Q_FOREACH (KisNodeSP currentNode, m_strokeData.clearedNodes()) { KIS_ASSERT_RECOVER_RETURN(currentNode); image()->addJob(m_strokeData.strokeId(), new TransformStrokeStrategy::TransformData( TransformStrokeStrategy::TransformData::PAINT_DEVICE, m_currentArgs, currentNode)); } } QWidget* KisToolTransform::createOptionWidget() { m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { cancelStroke(); image()->waitForDone(); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeData.strokeId()) return; ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); image()->waitForDone(); startStroke(savedArgs.mode(), true); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.h b/plugins/tools/tool_transform2/kis_tool_transform.h index 5c6abe204e..407889374e 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.h +++ b/plugins/tools/tool_transform2/kis_tool_transform.h @@ -1,333 +1,335 @@ /* * kis_tool_transform.h - part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * * 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_TOOL_TRANSFORM_H_ #define KIS_TOOL_TRANSFORM_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tool_transform_args.h" #include "tool_transform_changes_tracker.h" #include "kis_tool_transform_config_widget.h" #include "transform_transaction_properties.h" class KisCanvas2; class QTouchEvent; class KisTransformStrategyBase; class KisWarpTransformStrategy; class KisCageTransformStrategy; class KisLiquifyTransformStrategy; class KisFreeTransformStrategy; class KisPerspectiveTransformStrategy; /** * Transform tool * This tool offers several modes. * - Free Transform mode allows the user to translate, scale, shear, rotate and * apply a perspective transformation to a selection or the whole canvas. * - Warp mode allows the user to warp the selection of the canvas by grabbing * and moving control points placed on the image. The user can either work * with default control points, like a grid whose density can be modified, or * place the control points manually. The modifications made on the selected * pixels are applied only when the user clicks the Apply button : the * semi-transparent image displayed until the user click that button is only a * preview. * - Cage transform is similar to warp transform with control points exactly * placed on the outer boundary. The user draws a boundary polygon, the * vertices of which become control points. * - Perspective transform applies a two-point perspective transformation. The * user can manipulate the corners of the selection. If the vanishing points * of the resulting quadrilateral are on screen, the user can manipulate those * as well. * - Liquify transform transforms the selection by painting motions, as if the * user were fingerpainting. */ class KisToolTransform : public KisTool { Q_OBJECT Q_PROPERTY(TransformToolMode transformMode READ transformMode WRITE setTransformMode NOTIFY transformModeChanged) Q_PROPERTY(double translateX READ translateX WRITE setTranslateX NOTIFY freeTransformChanged) Q_PROPERTY(double translateY READ translateY WRITE setTranslateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateX READ rotateX WRITE setRotateX NOTIFY freeTransformChanged) Q_PROPERTY(double rotateY READ rotateY WRITE setRotateY NOTIFY freeTransformChanged) Q_PROPERTY(double rotateZ READ rotateZ WRITE setRotateZ NOTIFY freeTransformChanged) Q_PROPERTY(double scaleX READ scaleX WRITE setScaleX NOTIFY freeTransformChanged) Q_PROPERTY(double scaleY READ scaleY WRITE setScaleY NOTIFY freeTransformChanged) Q_PROPERTY(double shearX READ shearX WRITE setShearX NOTIFY freeTransformChanged) Q_PROPERTY(double shearY READ shearY WRITE setShearY NOTIFY freeTransformChanged) Q_PROPERTY(WarpType warpType READ warpType WRITE setWarpType NOTIFY warpTransformChanged) Q_PROPERTY(double warpFlexibility READ warpFlexibility WRITE setWarpFlexibility NOTIFY warpTransformChanged) Q_PROPERTY(int warpPointDensity READ warpPointDensity WRITE setWarpPointDensity NOTIFY warpTransformChanged) public: enum TransformToolMode { FreeTransformMode, WarpTransformMode, CageTransformMode, LiquifyTransformMode, PerspectiveTransformMode }; Q_ENUMS(TransformToolMode) enum WarpType { RigidWarpType, AffineWarpType, SimilitudeWarpType }; Q_ENUMS(WarpType) KisToolTransform(KoCanvasBase * canvas); virtual ~KisToolTransform(); virtual QWidget* createOptionWidget(); virtual void mousePressEvent(KoPointerEvent *e); virtual void mouseMoveEvent(KoPointerEvent *e); virtual void mouseReleaseEvent(KoPointerEvent *e); virtual void touchEvent(QTouchEvent *event); void beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action); + void activatePrimaryAction(); + void deactivatePrimaryAction(); void beginPrimaryAction(KoPointerEvent *event); void continuePrimaryAction(KoPointerEvent *event); void endPrimaryAction(KoPointerEvent *event); void activateAlternateAction(AlternateAction action); void deactivateAlternateAction(AlternateAction action); void beginAlternateAction(KoPointerEvent *event, AlternateAction action); void continueAlternateAction(KoPointerEvent *event, AlternateAction action); void endAlternateAction(KoPointerEvent *event, AlternateAction action); void paint(QPainter& gc, const KoViewConverter &converter); TransformToolMode transformMode() const; double translateX() const; double translateY() const; double rotateX() const; double rotateY() const; double rotateZ() const; double scaleX() const; double scaleY() const; double shearX() const; double shearY() const; WarpType warpType() const; double warpFlexibility() const; int warpPointDensity() const; bool wantsTouch() const { return true; } public Q_SLOTS: virtual void activate(ToolActivation toolActivation, const QSet &shapes); virtual void deactivate(); // Applies the current transformation to the original paint device and commits it to the undo stack void applyTransform(); void setTransformMode( KisToolTransform::TransformToolMode newMode ); void setTranslateX(double translateX); void setTranslateY(double translateY); void setRotateX(double rotation); void setRotateY(double rotation); void setRotateZ(double rotation); void setScaleX(double scaleX); void setScaleY(double scaleY); void setShearX(double shearX); void setShearY(double shearY); void setWarpType(WarpType type); void setWarpFlexibility(double flexibility); void setWarpPointDensity(int density); protected Q_SLOTS: virtual void resetCursorStyle(); Q_SIGNALS: void transformModeChanged(); void freeTransformChanged(); void warpTransformChanged(); public Q_SLOTS: void requestUndoDuringStroke(); void requestStrokeEnd(); void requestStrokeCancellation(); void canvasUpdateRequested(); void cursorOutlineUpdateRequested(const QPointF &imagePos); // Update the widget according to m_currentArgs void updateOptionWidget(); void resetRotationCenterButtonsRequested(); void imageTooBigRequested(bool value); private: bool clearDevices(KisNodeSP node, bool recursive); void transformDevices(KisNodeSP node, bool recursive); void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset); void endStroke(); void cancelStroke(); private: void outlineChanged(); // Sets the cursor according to mouse position (doesn't take shearing into account well yet) void setFunctionalCursor(); // Sets m_function according to mouse position and modifier void setTransformFunction(QPointF mousePos, Qt::KeyboardModifiers modifiers); void commitChanges(); bool tryInitTransformModeFromNode(KisNodeSP node); bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode); void initTransformMode(ToolTransformArgs::TransformMode mode); void initGuiAfterTransformMode(); void initThumbnailImage(KisPaintDeviceSP previewDevice); void updateSelectionPath(); void updateApplyResetAvailability(); private: ToolTransformArgs m_currentArgs; bool m_actuallyMoveWhileSelected; // true <=> selection has been moved while clicked KisPaintDeviceSP m_selectedPortionCache; struct StrokeData { StrokeData() {} StrokeData(KisStrokeId strokeId) : m_strokeId(strokeId) {} void clear() { m_strokeId.clear(); m_clearedNodes.clear(); } const KisStrokeId strokeId() const { return m_strokeId; } void addClearedNode(KisNodeSP node) { m_clearedNodes.append(node); } const QVector& clearedNodes() const { return m_clearedNodes; } private: KisStrokeId m_strokeId; QVector m_clearedNodes; }; StrokeData m_strokeData; bool m_workRecursively; QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations KisToolTransformConfigWidget *m_optionsWidget; KisCanvas2 *m_canvas; TransformTransactionProperties m_transaction; TransformChangesTracker m_changesTracker; /** * This artificial rect is used to store the image to flake * transformation. We check against this rect to get to know * whether zoom has changed. */ QRectF m_refRect; QScopedPointer m_warpStrategy; QScopedPointer m_cageStrategy; QScopedPointer m_liquifyStrategy; QScopedPointer m_freeStrategy; QScopedPointer m_perspectiveStrategy; KisTransformStrategyBase* currentStrategy() const; QPainterPath m_cursorOutline; private Q_SLOTS: void slotTrackerChangedConfig(); void slotUiChangedConfig(); void slotApplyTransform(); void slotResetTransform(); void slotRestartTransform(); void slotEditingFinished(); }; class KisToolTransformFactory : public KoToolFactoryBase { public: KisToolTransformFactory() : KoToolFactoryBase("KisToolTransform") { setToolTip(i18n("Transform a layer or a selection")); setSection(TOOL_TYPE_TRANSFORM); setIconName(koIconNameCStr("krita_tool_transform")); setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T)); setPriority(2); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } virtual ~KisToolTransformFactory() {} virtual KoToolBase * createTool(KoCanvasBase *canvas) { return new KisToolTransform(canvas); } }; #endif // KIS_TOOL_TRANSFORM_H_ diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index c9cb9c069f..efc3de939c 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp @@ -1,1220 +1,1285 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform_config_widget.h" #include #include "rotation_icons.h" #include "kis_canvas2.h" #include #include "kis_liquify_properties.h" #include "KisMainWindow.h" #include "KisViewManager.h" - #include "kis_transform_utils.h" template inline T sign(T x) { return x > 0 ? 1 : x == (T)0 ? 0 : -1; } const int KisToolTransformConfigWidget::DEFAULT_POINTS_PER_LINE = 3; KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent) : QWidget(parent), m_transaction(transaction), m_notificationsBlocked(0), m_uiSlotsBlocked(0), m_configChanged(false) { setupUi(this); showDecorationsBox->setIcon(KisIconUtils::loadIcon("krita_tool_transform")); chkWorkRecursively->setIcon(KisIconUtils::loadIcon("krita_tool_transform_recursive")); flipXButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_x")); flipYButton->setIcon(KisIconUtils::loadIcon("transform_icons_mirror_y")); rotateCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_cw")); rotateCCWButton->setIcon(KisIconUtils::loadIcon("transform_icons_rotate_ccw")); chkWorkRecursively->setChecked(workRecursively); connect(chkWorkRecursively, SIGNAL(toggled(bool)), this, SIGNAL(sigRestartTransform())); // Init Filter combo cmbFilter->setIDList(KisFilterStrategyRegistry::instance()->listKeys()); cmbFilter->setCurrent("Bicubic"); cmbFilter->setToolTip(i18nc("@info:tooltip", - "Select filtering mode:\n" - "" - "Bilinear for areas with uniform color to avoid artifacts" - "Bicubic for smoother results" - "Lanczos3 for sharp results. May produce aerials." - "")); + "

Select filtering mode:\n" + "

    " + "
  • Bilinear for areas with uniform color to avoid artifacts
  • " + "
  • Bicubic for smoother results
  • " + "
  • Lanczos3 for sharp results. May produce aerials." + "

")); connect(cmbFilter, SIGNAL(activated(const KoID &)), this, SLOT(slotFilterChanged(const KoID &))); // Init Warp Type combo cmbWarpType->insertItem(KisWarpTransformWorker::AFFINE_TRANSFORM,i18n("Default (Affine)")); cmbWarpType->insertItem(KisWarpTransformWorker::RIGID_TRANSFORM,i18n("Strong (Rigid)")); cmbWarpType->insertItem(KisWarpTransformWorker::SIMILITUDE_TRANSFORM,i18n("Strongest (Similitude)")); cmbWarpType->setCurrentIndex(KisWarpTransformWorker::AFFINE_TRANSFORM); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWarpTypeChanged(int))); // Init Rotation Center buttons m_handleDir[0] = QPointF(1, 0); m_handleDir[1] = QPointF(1, -1); m_handleDir[2] = QPointF(0, -1); m_handleDir[3] = QPointF(-1, -1); m_handleDir[4] = QPointF(-1, 0); m_handleDir[5] = QPointF(-1, 1); m_handleDir[6] = QPointF(0, 1); m_handleDir[7] = QPointF(1, 1); m_handleDir[8] = QPointF(0, 0); // also add the center m_rotationCenterButtons = new QButtonGroup(0); // we set the ids to match m_handleDir m_rotationCenterButtons->addButton(middleRightButton, 0); m_rotationCenterButtons->addButton(topRightButton, 1); m_rotationCenterButtons->addButton(middleTopButton, 2); m_rotationCenterButtons->addButton(topLeftButton, 3); m_rotationCenterButtons->addButton(middleLeftButton, 4); m_rotationCenterButtons->addButton(bottomLeftButton, 5); m_rotationCenterButtons->addButton(middleBottomButton, 6); m_rotationCenterButtons->addButton(bottomRightButton, 7); m_rotationCenterButtons->addButton(centerButton, 8); QToolButton *nothingSelected = new QToolButton(0); nothingSelected->setCheckable(true); nothingSelected->setAutoExclusive(true); nothingSelected->hide(); // a convenient button for when no button is checked in the group m_rotationCenterButtons->addButton(nothingSelected, 9); // initialize values for free transform sliders shearXBox->setSuffix(i18n(" px")); shearYBox->setSuffix(i18n(" px")); shearXBox->setRange(-5.0, 5.0, 2); shearYBox->setRange(-5.0, 5.0, 2); shearXBox->setSingleStep(0.01); shearYBox->setSingleStep(0.01); shearXBox->setValue(0.0); shearYBox->setValue(0.0); translateXBox->setSuffix(i18n(" px")); translateYBox->setSuffix(i18n(" px")); translateXBox->setRange(-10000, 10000); translateYBox->setRange(-10000, 10000); scaleXBox->setSuffix("%"); scaleYBox->setSuffix("%"); scaleXBox->setRange(-10000, 10000); scaleYBox->setRange(-10000, 10000); scaleXBox->setValue(100.0); scaleYBox->setValue(100.0); m_scaleRatio = 1.0; aXBox->setSuffix(QChar(Qt::Key_degree)); aYBox->setSuffix(QChar(Qt::Key_degree)); aZBox->setSuffix(QChar(Qt::Key_degree)); aXBox->setRange(0.0, 360.0, 2); aYBox->setRange(0.0, 360.0, 2); aZBox->setRange(0.0, 360.0, 2); aXBox->setValue(0.0); aYBox->setValue(0.0); aZBox->setValue(0.0); aXBox->setSingleStep(1.0); aYBox->setSingleStep(1.0); aZBox->setSingleStep(1.0); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(slotRotationCenterChanged(int))); + connect(btnTransformAroundPivotPoint, SIGNAL(clicked(bool)), this, SLOT(slotTransformAroundRotationCenter(bool))); // Init Free Transform Values connect(scaleXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleX(int))); connect(scaleYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetScaleY(int))); connect(shearXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearX(qreal))); connect(shearYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetShearY(qreal))); connect(translateXBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateX(int))); connect(translateYBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetTranslateY(int))); connect(aXBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAX(qreal))); connect(aYBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAY(qreal))); connect(aZBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAZ(qreal))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotSetKeepAspectRatio(bool))); connect(flipXButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipX())); connect(flipYButton, SIGNAL(clicked(bool)), this, SLOT(slotFlipY())); connect(rotateCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCW())); connect(rotateCCWButton, SIGNAL(clicked(bool)), this, SLOT(slotRotateCCW())); // toggle visibility of different free buttons connect(freeMoveRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeRotationRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeScaleRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); connect(freeShearRadioButton, SIGNAL(clicked(bool)), SLOT(slotTransformAreaVisible(bool))); // only first group for free transform rotationGroup->hide(); moveGroup->show(); scaleGroup->hide(); shearGroup->hide(); - // Init Warp Transform Values + // Init Warp Transform Values alphaBox->setSingleStep(0.1); alphaBox->setRange(0, 10, 1); connect(alphaBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetWarpAlpha(qreal))); connect(densityBox, SIGNAL(valueChanged(int)), this, SLOT(slotSetWarpDensity(int))); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpDefaultPointsButtonClicked(bool))); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(slotWarpCustomPointsButtonClicked(bool))); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpLockPointsButtonClicked())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(slotWarpResetPointsButtonClicked())); // Init Cage Transform Values cageTransformButtonGroup->setId(cageAddEditRadio, 0); // we need to set manually since Qt Designer generates negative by default cageTransformButtonGroup->setId(cageDeformRadio, 1); connect(cageTransformButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotCageOptionsChanged(int))); // Init Liquify Transform Values liquifySizeSlider->setRange(KisLiquifyProperties::minSize(), KisLiquifyProperties::maxSize(), 2); liquifySizeSlider->setExponentRatio(4); liquifySizeSlider->setValue(50.0); connect(liquifySizeSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySizeChanged(qreal))); liquifySizeSlider->setToolTip(i18nc("@info:tooltip", "Size of the deformation brush")); liquifyAmountSlider->setRange(0.0, 1.0, 2); liquifyAmountSlider->setValue(1.0); connect(liquifyAmountSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyAmountChanged(qreal))); liquifyAmountSlider->setToolTip(i18nc("@info:tooltip", "Amount of the deformation you get")); liquifyFlowSlider->setRange(0.0, 1.0, 2); liquifyFlowSlider->setValue(1.0); connect(liquifyFlowSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifyFlowChanged(qreal))); liquifyFlowSlider->setToolTip(i18nc("@info:tooltip", "When in non-buildup mode, shows how fast the deformation limit is reached.")); buidupModeComboBox->setCurrentIndex(0); // set to build-up mode by default connect(buidupModeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(liquifyBuildUpChanged(int))); buidupModeComboBox->setToolTip(i18nc("@info:tooltip", "Switch between Build Up and Wash mode of painting. Build Up mode adds deformations one on top of the other without any limits. Wash mode gradually deforms the piece to the selected deformation level.")); liquifySpacingSlider->setRange(0.0, 3.0, 2); liquifySizeSlider->setExponentRatio(3); liquifySpacingSlider->setSingleStep(0.01); liquifySpacingSlider->setValue(0.2); connect(liquifySpacingSlider, SIGNAL(valueChanged(qreal)), this, SLOT(liquifySpacingChanged(qreal))); liquifySpacingSlider->setToolTip(i18nc("@info:tooltip", "Space between two sequential applications of the deformation")); liquifySizePressureBox->setChecked(true); connect(liquifySizePressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifySizePressureChanged(bool))); liquifySizePressureBox->setToolTip(i18nc("@info:tooltip", "Scale Size value according to current stylus pressure")); liquifyAmountPressureBox->setChecked(true); connect(liquifyAmountPressureBox, SIGNAL(toggled(bool)), this, SLOT(liquifyAmountPressureChanged(bool))); liquifyAmountPressureBox->setToolTip(i18nc("@info:tooltip", "Scale Amount value according to current stylus pressure")); liquifyReverseDirectionChk->setChecked(false); connect(liquifyReverseDirectionChk, SIGNAL(toggled(bool)), this, SLOT(liquifyReverseDirectionChanged(bool))); liquifyReverseDirectionChk->setToolTip(i18nc("@info:tooltip", "Reverse direction of the current deformation tool")); QSignalMapper *liquifyModeMapper = new QSignalMapper(this); connect(liquifyMove, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyScale, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyRotate, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyOffset, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); connect(liquifyUndo, SIGNAL(toggled(bool)), liquifyModeMapper, SLOT(map())); liquifyModeMapper->setMapping(liquifyMove, (int)KisLiquifyProperties::MOVE); liquifyModeMapper->setMapping(liquifyScale, (int)KisLiquifyProperties::SCALE); liquifyModeMapper->setMapping(liquifyRotate, (int)KisLiquifyProperties::ROTATE); liquifyModeMapper->setMapping(liquifyOffset, (int)KisLiquifyProperties::OFFSET); liquifyModeMapper->setMapping(liquifyUndo, (int)KisLiquifyProperties::UNDO); connect(liquifyModeMapper, SIGNAL(mapped(int)), SLOT(slotLiquifyModeChanged(int))); liquifyMove->setToolTip(i18nc("@info:tooltip", "Move: drag the image along the brush stroke")); liquifyScale->setToolTip(i18nc("@info:tooltip", "Scale: grow/shrink image under cursor")); liquifyRotate->setToolTip(i18nc("@info:tooltip", "Rotate: twirl image under cursor")); liquifyOffset->setToolTip(i18nc("@info:tooltip", "Offset: shift the image to the right of the stroke direction")); liquifyUndo->setToolTip(i18nc("@info:tooltip", "Undo: erase actions of other tools")); // Connect all edit boxes to the Editing Finished signal connect(densityBox, SIGNAL(editingFinished()), this, SLOT(notifyEditingFinished())); // Connect other widget (not having editingFinished signal) to // the same slot. From Qt 4.6 onwards the sequence of the signal // delivery is definite. connect(cmbFilter, SIGNAL(activated(const KoID &)), this, SLOT(notifyEditingFinished())); connect(cmbWarpType, SIGNAL(currentIndexChanged(int)), this, SLOT(notifyEditingFinished())); connect(m_rotationCenterButtons, SIGNAL(buttonPressed(int)), this, SLOT(notifyEditingFinished())); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(notifyEditingFinished())); connect(defaultRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(customRadioButton, SIGNAL(clicked(bool)), this, SLOT(notifyEditingFinished())); connect(lockUnlockPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); connect(resetPointsButton, SIGNAL(clicked()), this, SLOT(notifyEditingFinished())); // Liquify // // liquify brush options do not affect the image directly and are not // saved to undo, so we don't emit notifyEditingFinished() for them // Connect Apply/Reset buttons connect(buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(slotButtonBoxClicked(QAbstractButton *))); // Mode switch buttons connect(freeTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetFreeTransformModeButtonClicked(bool))); connect(warpButton, SIGNAL(clicked(bool)), this, SLOT(slotSetWarpModeButtonClicked(bool))); connect(cageButton, SIGNAL(clicked(bool)), this, SLOT(slotSetCageModeButtonClicked(bool))); connect(perspectiveTransformButton, SIGNAL(clicked(bool)), this, SLOT(slotSetPerspectiveModeButtonClicked(bool))); connect(liquifyButton, SIGNAL(clicked(bool)), this, SLOT(slotSetLiquifyModeButtonClicked(bool))); // Connect Decorations switcher connect(showDecorationsBox, SIGNAL(toggled(bool)), canvas, SLOT(updateCanvas())); tooBigLabelWidget->hide(); connect(canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); slotUpdateIcons(); } void KisToolTransformConfigWidget::slotUpdateIcons() { freeTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_main")); warpButton->setIcon(KisIconUtils::loadIcon("transform_icons_warp")); cageButton->setIcon(KisIconUtils::loadIcon("transform_icons_cage")); perspectiveTransformButton->setIcon(KisIconUtils::loadIcon("transform_icons_perspective")); liquifyButton->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_main")); liquifyMove->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_move")); liquifyScale->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_resize")); liquifyRotate->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_rotate")); liquifyOffset->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_offset")); liquifyUndo->setIcon(KisIconUtils::loadIcon("transform_icons_liquify_erase")); middleRightButton->setIcon(KisIconUtils::loadIcon("arrow-right")); topRightButton->setIcon(KisIconUtils::loadIcon("arrow-topright")); middleTopButton->setIcon(KisIconUtils::loadIcon("arrow-up")); topLeftButton->setIcon(KisIconUtils::loadIcon("arrow-topleft")); middleLeftButton->setIcon(KisIconUtils::loadIcon("arrow-left")); bottomLeftButton->setIcon(KisIconUtils::loadIcon("arrow-downleft")); middleBottomButton->setIcon(KisIconUtils::loadIcon("arrow-down")); bottomRightButton->setIcon(KisIconUtils::loadIcon("arrow-downright")); + btnTransformAroundPivotPoint->setIcon(KisIconUtils::loadIcon("pivot-point")); + // pressure icons liquifySizePressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); liquifyAmountPressureBox->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } double KisToolTransformConfigWidget::radianToDegree(double rad) { double piX2 = 2 * M_PI; if (rad < 0 || rad >= piX2) { rad = fmod(rad, piX2); if (rad < 0) { rad += piX2; } } return (rad * 360. / piX2); } double KisToolTransformConfigWidget::degreeToRadian(double degree) { if (degree < 0. || degree >= 360.) { degree = fmod(degree, 360.); if (degree < 0) degree += 360.; } return (degree * M_PI / 180.); } void KisToolTransformConfigWidget::updateLiquifyControls() { blockUiSlots(); ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); const bool useWashMode = props->useWashMode(); liquifySizeSlider->setValue(props->size()); liquifyAmountSlider->setValue(props->amount()); liquifyFlowSlider->setValue(props->flow()); buidupModeComboBox->setCurrentIndex(useWashMode); liquifySpacingSlider->setValue(props->spacing()); liquifySizePressureBox->setChecked(props->sizeHasPressure()); liquifyAmountPressureBox->setChecked(props->amountHasPressure()); liquifyReverseDirectionChk->setChecked(props->reverseDirection()); KisLiquifyProperties::LiquifyMode mode = static_cast(props->mode()); bool canInverseDirection = mode != KisLiquifyProperties::UNDO; bool canUseWashMode = mode != KisLiquifyProperties::UNDO; liquifyReverseDirectionChk->setEnabled(canInverseDirection); liquifyFlowSlider->setEnabled(canUseWashMode && useWashMode); buidupModeComboBox->setEnabled(canUseWashMode); const qreal maxAmount = canUseWashMode ? 5.0 : 1.0; liquifyAmountSlider->setRange(0.0, maxAmount, 2); unblockUiSlots(); } void KisToolTransformConfigWidget::slotLiquifyModeChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); KisLiquifyProperties::LiquifyMode mode = static_cast(value); if (mode == props->mode()) return; props->setMode(mode); props->loadMode(); updateLiquifyControls(); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizeChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSize(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmount(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyFlowChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setFlow(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyBuildUpChanged(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setUseWashMode(value); // 0 == build up mode / 1 == wash mode notifyConfigChanged(); // we need to enable/disable flow slider updateLiquifyControls(); } void KisToolTransformConfigWidget::liquifySpacingChanged(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSpacing(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifySizePressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setSizeHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyAmountPressureChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setAmountHasPressure(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::liquifyReverseDirectionChanged(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); KisLiquifyProperties *props = config->liquifyProperties(); props->setReverseDirection(value); notifyConfigChanged(); } void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) { blockUiSlots(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM || config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { quickTransformGroup->setEnabled(config.mode() == ToolTransformArgs::FREE_TRANSFORM); stackedWidget->setCurrentIndex(0); bool freeTransformIsActive = config.mode() == ToolTransformArgs::FREE_TRANSFORM; if (freeTransformIsActive) { - freeTransformButton->setChecked(true); + freeTransformButton->setChecked(true); } else { perspectiveTransformButton->setChecked(true); } aXBox->setEnabled(freeTransformIsActive); aYBox->setEnabled(freeTransformIsActive); aZBox->setEnabled(freeTransformIsActive); freeRotationRadioButton->setEnabled(freeTransformIsActive); scaleXBox->setValue(config.scaleX() * 100.); scaleYBox->setValue(config.scaleY() * 100.); shearXBox->setValue(config.shearX()); shearYBox->setValue(config.shearY()); const QPointF anchorPoint = config.originalCenter() + config.rotationCenterOffset(); const KisTransformUtils::MatricesPack m(config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); translateXBox->setValue(anchorPointView.x()); translateYBox->setValue(anchorPointView.y()); aXBox->setValue(radianToDegree(config.aX())); aYBox->setValue(radianToDegree(config.aY())); aZBox->setValue(radianToDegree(config.aZ())); aspectButton->setKeepAspectRatio(config.keepAspectRatio()); cmbFilter->setCurrent(config.filterId()); QPointF pt = m_transaction->currentConfig()->rotationCenterOffset(); pt.rx() /= m_transaction->originalHalfWidth(); pt.ry() /= m_transaction->originalHalfHeight(); for (int i = 0; i < 9; i++) { if (qFuzzyCompare(m_handleDir[i].x(), pt.x()) && qFuzzyCompare(m_handleDir[i].y(), pt.y())) { m_rotationCenterButtons->button(i)->setChecked(true); break; } } + + btnTransformAroundPivotPoint->setChecked(config.transformAroundRotationCenter()); + } else if (config.mode() == ToolTransformArgs::WARP) { stackedWidget->setCurrentIndex(1); warpButton->setChecked(true); if (config.defaultPoints()) { densityBox->setValue(std::sqrt(config.numPoints())); } cmbWarpType->setCurrentIndex((int)config.warpType()); defaultRadioButton->setChecked(config.defaultPoints()); customRadioButton->setChecked(!config.defaultPoints()); densityBox->setEnabled(config.defaultPoints()); customWarpWidget->setEnabled(!config.defaultPoints()); updateLockPointsButtonCaption(); } else if (config.mode() == ToolTransformArgs::CAGE) { // default UI options resetUIOptions(); // we need at least 3 point before we can start actively deforming if (config.origPoints().size() >= 3) { cageTransformDirections->setText(i18n("Switch between editing and deforming cage")); cageAddEditRadio->setVisible(true); cageDeformRadio->setVisible(true); // update to correct radio button if (config.isEditingTransformPoints()) cageAddEditRadio->setChecked(true); else cageDeformRadio->setChecked(true); } } else if (config.mode() == ToolTransformArgs::LIQUIFY) { stackedWidget->setCurrentIndex(3); liquifyButton->setChecked(true); const KisLiquifyProperties *props = config.liquifyProperties(); switch (props->mode()) { case KisLiquifyProperties::MOVE: liquifyMove->setChecked(true); break; case KisLiquifyProperties::SCALE: liquifyScale->setChecked(true); break; case KisLiquifyProperties::ROTATE: liquifyRotate->setChecked(true); break; case KisLiquifyProperties::OFFSET: liquifyOffset->setChecked(true); break; case KisLiquifyProperties::UNDO: liquifyUndo->setChecked(true); break; case KisLiquifyProperties::N_MODES: qFatal("Unsupported mode"); } updateLiquifyControls(); } unblockUiSlots(); } void KisToolTransformConfigWidget::updateLockPointsButtonCaption() { ToolTransformArgs *config = m_transaction->currentConfig(); if (config->isEditingTransformPoints()) { lockUnlockPointsButton->setText(i18n("Lock Points")); } else { lockUnlockPointsButton->setText(i18n("Unlock Points")); } } void KisToolTransformConfigWidget::setApplyResetDisabled(bool disabled) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); Q_ASSERT(applyButton); Q_ASSERT(resetButton); applyButton->setDisabled(disabled); resetButton->setDisabled(disabled); } void KisToolTransformConfigWidget::resetRotationCenterButtons() { int checkedId = m_rotationCenterButtons->checkedId(); if (checkedId >= 0 && checkedId <= 8) { // uncheck the current checked button m_rotationCenterButtons->button(9)->setChecked(true); } } bool KisToolTransformConfigWidget::workRecursively() const { return chkWorkRecursively->isChecked(); } void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value) { tooBigLabelWidget->setVisible(value); } bool KisToolTransformConfigWidget::showDecorations() const { return showDecorationsBox->isChecked(); } void KisToolTransformConfigWidget::blockNotifications() { m_notificationsBlocked++; } void KisToolTransformConfigWidget::unblockNotifications() { m_notificationsBlocked--; } void KisToolTransformConfigWidget::notifyConfigChanged() { if (!m_notificationsBlocked) { emit sigConfigChanged(); } m_configChanged = true; } void KisToolTransformConfigWidget::blockUiSlots() { m_uiSlotsBlocked++; } void KisToolTransformConfigWidget::unblockUiSlots() { m_uiSlotsBlocked--; } void KisToolTransformConfigWidget::notifyEditingFinished() { if (m_uiSlotsBlocked || m_notificationsBlocked || !m_configChanged) return; emit sigEditingFinished(); m_configChanged = false; } void KisToolTransformConfigWidget::resetUIOptions() { // reset tool states since we are done (probably can encapsulate this later) ToolTransformArgs *config = m_transaction->currentConfig(); if (config->mode() == ToolTransformArgs::CAGE) { cageAddEditRadio->setVisible(false); cageAddEditRadio->setChecked(true); cageDeformRadio->setVisible(false); cageTransformDirections->setText(i18n("Create 3 points on the canvas to begin")); // ensure we are on the right options view stackedWidget->setCurrentIndex(2); } } void KisToolTransformConfigWidget::slotButtonBoxClicked(QAbstractButton *button) { QAbstractButton *applyButton = buttonBox->button(QDialogButtonBox::Apply); QAbstractButton *resetButton = buttonBox->button(QDialogButtonBox::Reset); resetUIOptions(); if (button == applyButton) { emit sigApplyTransform(); } else if (button == resetButton) { emit sigResetTransform(); } } void KisToolTransformConfigWidget::slotSetFreeTransformModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(freeTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::FREE_TRANSFORM); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetWarpModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(warpButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::WARP); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetCageModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(cageButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::CAGE); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetLiquifyModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(liquifyButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::LIQUIFY); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotSetPerspectiveModeButtonClicked(bool value) { if (!value) return; lblTransformType->setText(perspectiveTransformButton->toolTip()); ToolTransformArgs *config = m_transaction->currentConfig(); config->setMode(ToolTransformArgs::PERSPECTIVE_4POINT); emit sigResetTransform(); } void KisToolTransformConfigWidget::slotFilterChanged(const KoID &filterId) { ToolTransformArgs *config = m_transaction->currentConfig(); config->setFilterId(filterId.id()); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotRotationCenterChanged(int index) { if (m_uiSlotsBlocked) return; if (index >= 0 && index <= 8) { ToolTransformArgs *config = m_transaction->currentConfig(); double i = m_handleDir[index].x(); double j = m_handleDir[index].y(); config->setRotationCenterOffset(QPointF(i * m_transaction->originalHalfWidth(), j * m_transaction->originalHalfHeight())); notifyConfigChanged(); updateConfig(*config); } +} +void KisToolTransformConfigWidget::slotTransformAroundRotationCenter(bool value) +{ + if (m_uiSlotsBlocked) return; + ToolTransformArgs *config = m_transaction->currentConfig(); + config->setTransformAroundRotationCenter(value); + notifyConfigChanged(); + notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setScaleX(value / 100.); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setScaleX(value / 100.); + } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int( value/ m_scaleRatio ); scaleYBox->blockSignals(true); scaleYBox->setValue(calculatedValue); config->setScaleY(calculatedValue / 100.); scaleYBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetScaleY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setScaleY(value / 100.); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setScaleY(value / 100.); + } if (config->keepAspectRatio()) { blockNotifications(); int calculatedValue = int(m_scaleRatio * value); scaleXBox->blockSignals(true); scaleXBox->setValue(calculatedValue); config->setScaleX(calculatedValue / 100.); scaleXBox->blockSignals(false); unblockNotifications(); } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setShearX((double)value); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setShearX((double)value); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetShearY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setShearY((double)value); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setShearY((double)value); + } + + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetTranslateX(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(value, anchorPointView.y()); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetTranslateY(int value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); const QPointF anchorPoint = config->originalCenter() + config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*config); const QPointF anchorPointView = m.finalTransform().map(anchorPoint); const QPointF newAnchorPointView(anchorPointView.x(), value); config->setTransformedCenter(config->transformedCenter() + newAnchorPointView - anchorPointView); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetAX(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setAX(degreeToRadian((double)value)); + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setAX(degreeToRadian((double)value)); + } notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAY(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setAY(degreeToRadian((double)value)); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setAY(degreeToRadian((double)value)); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetAZ(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); - config->setAZ(degreeToRadian((double)value)); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setAZ(degreeToRadian((double)value)); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipX() { ToolTransformArgs *config = m_transaction->currentConfig(); - config->setScaleX(config->scaleX() * -1); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setScaleX(config->scaleX() * -1); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotFlipY() { ToolTransformArgs *config = m_transaction->currentConfig(); - config->setScaleY(config->scaleY() * -1); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setScaleY(config->scaleY() * -1); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCW() { ToolTransformArgs *config = m_transaction->currentConfig(); - config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setAZ(normalizeAngle(config->aZ() + M_PI_2)); + } + notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotRotateCCW() { ToolTransformArgs *config = m_transaction->currentConfig(); - config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); + + { + KisTransformUtils::AnchorHolder keeper(config->transformAroundRotationCenter(), config); + config->setAZ(normalizeAngle(config->aZ() - M_PI_2)); + } + notifyConfigChanged(); notifyEditingFinished(); } // change free transform setting we want to alter (all radio buttons call this) void KisToolTransformConfigWidget::slotTransformAreaVisible(bool value) { Q_UNUSED(value); //QCheckBox sender = (QCheckBox)(*)(QObject::sender()); QString senderName = QObject::sender()->objectName(); // only show setting with what we have selected rotationGroup->hide(); shearGroup->hide(); scaleGroup->hide(); moveGroup->hide(); if ("freeMoveRadioButton" == senderName) { moveGroup->show(); } else if ("freeShearRadioButton" == senderName) { shearGroup->show(); } else if ("freeScaleRadioButton" == senderName) { scaleGroup->show(); } else { rotationGroup->show(); } } void KisToolTransformConfigWidget::slotSetKeepAspectRatio(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setKeepAspectRatio(value); if (value) { blockNotifications(); int tmpXScaleBox = scaleXBox->value(); int tmpYScaleBox = scaleYBox->value(); m_scaleRatio = (tmpXScaleBox / (double) tmpYScaleBox); unblockNotifications(); } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotSetWarpAlpha(qreal value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setAlpha((double)value); notifyConfigChanged(); notifyEditingFinished(); } void KisToolTransformConfigWidget::slotSetWarpDensity(int value) { if (m_uiSlotsBlocked) return; setDefaultWarpPoints(value); } void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) { if (pointsPerLine < 0) { pointsPerLine = DEFAULT_POINTS_PER_LINE; } int nbPoints = pointsPerLine * pointsPerLine; QVector origPoints(nbPoints); QVector transfPoints(nbPoints); qreal gridSpaceX, gridSpaceY; if (nbPoints == 1) { //there is actually no grid origPoints[0] = m_transaction->originalCenterGeometric(); transfPoints[0] = m_transaction->originalCenterGeometric(); } else if (nbPoints > 1) { gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1); gridSpaceY = m_transaction->originalRect().height() / (pointsPerLine - 1); double y = m_transaction->originalRect().top(); for (int i = 0; i < pointsPerLine; ++i) { double x = m_transaction->originalRect().left(); for (int j = 0 ; j < pointsPerLine; ++j) { origPoints[i * pointsPerLine + j] = QPointF(x, y); transfPoints[i * pointsPerLine + j] = QPointF(x, y); x += gridSpaceX; } y += gridSpaceY; } } ToolTransformArgs *config = m_transaction->currentConfig(); config->setDefaultPoints(nbPoints > 0); config->setPoints(origPoints, transfPoints); notifyConfigChanged(); } void KisToolTransformConfigWidget::activateCustomWarpPoints(bool enabled) { ToolTransformArgs *config = m_transaction->currentConfig(); densityBox->setEnabled(!enabled); customWarpWidget->setEnabled(enabled); if (!enabled) { config->setEditingTransformPoints(false); setDefaultWarpPoints(densityBox->value()); } else { config->setEditingTransformPoints(true); setDefaultWarpPoints(0); } updateLockPointsButtonCaption(); } void KisToolTransformConfigWidget::slotWarpDefaultPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(!value); } void KisToolTransformConfigWidget::slotWarpCustomPointsButtonClicked(bool value) { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(value); } void KisToolTransformConfigWidget::slotWarpResetPointsButtonClicked() { if (m_uiSlotsBlocked) return; activateCustomWarpPoints(true); } void KisToolTransformConfigWidget::slotWarpLockPointsButtonClicked() { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->setEditingTransformPoints(!config->isEditingTransformPoints()); if (config->isEditingTransformPoints()) { // reinit the transf points to their original value ToolTransformArgs *config = m_transaction->currentConfig(); int nbPoints = config->origPoints().size(); for (int i = 0; i < nbPoints; ++i) { config->transfPoint(i) = config->origPoint(i); } } updateLockPointsButtonCaption(); notifyConfigChanged(); } void KisToolTransformConfigWidget::slotWarpTypeChanged(int index) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); switch (index) { case KisWarpTransformWorker::AFFINE_TRANSFORM: case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: case KisWarpTransformWorker::RIGID_TRANSFORM: config->setWarpType((KisWarpTransformWorker::WarpType)index); break; default: config->setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; } notifyConfigChanged(); } void KisToolTransformConfigWidget::slotCageOptionsChanged(int value) { if ( value == 0) { slotEditCagePoints(true); } else { slotEditCagePoints(false); } notifyEditingFinished(); } void KisToolTransformConfigWidget::slotEditCagePoints(bool value) { if (m_uiSlotsBlocked) return; ToolTransformArgs *config = m_transaction->currentConfig(); config->refTransformedPoints() = config->origPoints(); config->setEditingTransformPoints(value); notifyConfigChanged(); } diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h index a23fc280a5..364cc3f3e1 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h +++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h @@ -1,148 +1,149 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H #define __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H #include "transform_transaction_properties.h" #include "tool_transform_args.h" #include "ui_wdg_tool_transform.h" class KisCanvas2; class KisToolTransformConfigWidget : public QWidget, private Ui::WdgToolTransform { Q_OBJECT public: KisToolTransformConfigWidget(TransformTransactionProperties *transaction, KisCanvas2 *canvas, bool workRecursively, QWidget *parent); void setApplyResetDisabled(bool disabled); void resetRotationCenterButtons(); void setDefaultWarpPoints(int pointsPerLine = -1); void setTooBigLabelVisible(bool value); bool showDecorations() const; bool workRecursively() const; public Q_SLOTS: void updateConfig(const ToolTransformArgs &config); void slotUpdateIcons(); Q_SIGNALS: void sigConfigChanged(); void sigApplyTransform(); void sigResetTransform(); void sigRestartTransform(); void sigEditingFinished(); public Q_SLOTS: void slotFilterChanged(const KoID &filter); void slotWarpTypeChanged(int index); void slotRotationCenterChanged(int index); + void slotTransformAroundRotationCenter(bool value); void slotSetScaleX(int value); void slotSetScaleY(int value); void slotSetShearX(qreal value); void slotSetShearY(qreal value); void slotSetTranslateX(int value); void slotSetTranslateY(int value); void slotSetAX(qreal value); void slotSetAY(qreal value); void slotSetAZ(qreal value); void slotFlipX(); void slotFlipY(); void slotRotateCW(); void slotRotateCCW(); void slotSetWarpAlpha(qreal value); void slotSetWarpDensity(int value); void slotSetKeepAspectRatio(bool value); void slotTransformAreaVisible(bool value); void slotWarpDefaultPointsButtonClicked(bool value); void slotWarpCustomPointsButtonClicked(bool value); void slotWarpLockPointsButtonClicked(); void slotWarpResetPointsButtonClicked(); void slotSetFreeTransformModeButtonClicked(bool); void slotSetWarpModeButtonClicked(bool); void slotSetCageModeButtonClicked(bool); void slotCageOptionsChanged(int); void slotSetPerspectiveModeButtonClicked(bool); void slotSetLiquifyModeButtonClicked(bool); void slotButtonBoxClicked(QAbstractButton *button); void slotEditCagePoints(bool value); void liquifySizeChanged(qreal value); void liquifyAmountChanged(qreal value); void liquifyFlowChanged(qreal value); void liquifyBuildUpChanged(int value); void liquifySpacingChanged(qreal value); void liquifySizePressureChanged(bool value); void liquifyAmountPressureChanged(bool value); void liquifyReverseDirectionChanged(bool value); void slotLiquifyModeChanged(int value); void notifyEditingFinished(); private: // rad being in |R, the returned value is in [0; 360] double radianToDegree(double rad); // degree being in |R, the returned value is in [0; 2*M_PI] double degreeToRadian(double degree); void blockNotifications(); void unblockNotifications(); void notifyConfigChanged(); void blockUiSlots(); void unblockUiSlots(); void activateCustomWarpPoints(bool enabled); void updateLockPointsButtonCaption(); void updateLiquifyControls(); void resetUIOptions(); private: static const int DEFAULT_POINTS_PER_LINE; private: TransformTransactionProperties *m_transaction; QPointF m_handleDir[9]; QButtonGroup *m_rotationCenterButtons; int m_notificationsBlocked; int m_uiSlotsBlocked; double m_scaleRatio; bool m_configChanged; }; #endif /* __KIS_TOOL_TRANSFORM_CONFIG_WIDGET_H */ diff --git a/plugins/tools/tool_transform2/kis_transform_strategy_base.cpp b/plugins/tools/tool_transform2/kis_transform_strategy_base.cpp index aab7b55d46..1e7603ef39 100644 --- a/plugins/tools/tool_transform2/kis_transform_strategy_base.cpp +++ b/plugins/tools/tool_transform2/kis_transform_strategy_base.cpp @@ -1,96 +1,104 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transform_strategy_base.h" #include #include #include "KoPointerEvent.h" struct KisTransformStrategyBase::Private { QTransform thumbToImageTransform; QImage originalImage; }; KisTransformStrategyBase::KisTransformStrategyBase() : m_d(new Private()) { } KisTransformStrategyBase::~KisTransformStrategyBase() { } QPainterPath KisTransformStrategyBase::getCursorOutline() const { return QPainterPath(); } +void KisTransformStrategyBase::activatePrimaryAction() +{ +} + +void KisTransformStrategyBase::deactivatePrimaryAction() +{ +} + QImage KisTransformStrategyBase::originalImage() const { return m_d->originalImage; } QTransform KisTransformStrategyBase::thumbToImageTransform() const { return m_d->thumbToImageTransform; } void KisTransformStrategyBase::setThumbnailImage(const QImage &image, QTransform thumbToImageTransform) { m_d->originalImage = image; m_d->thumbToImageTransform = thumbToImageTransform; } bool KisTransformStrategyBase::acceptsClicks() const { return false; } void KisTransformStrategyBase::activateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); } void KisTransformStrategyBase::deactivateAlternateAction(KisTool::AlternateAction action) { Q_UNUSED(action); } bool KisTransformStrategyBase::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); return false; } void KisTransformStrategyBase::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); } bool KisTransformStrategyBase::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { Q_UNUSED(event); Q_UNUSED(action); return false; } diff --git a/plugins/tools/tool_transform2/kis_transform_strategy_base.h b/plugins/tools/tool_transform2/kis_transform_strategy_base.h index d0a3d1cb5f..cb1aa1326c 100644 --- a/plugins/tools/tool_transform2/kis_transform_strategy_base.h +++ b/plugins/tools/tool_transform2/kis_transform_strategy_base.h @@ -1,74 +1,77 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TRANSFORM_STRATEGY_BASE_H #define __KIS_TRANSFORM_STRATEGY_BASE_H #include #include #include "kis_tool.h" class QImage; class QTransform; class QPainter; class QCursor; class KoPointerEvent; class QPainterPath; class KisTransformStrategyBase : public QObject { public: KisTransformStrategyBase(); ~KisTransformStrategyBase(); QImage originalImage() const; QTransform thumbToImageTransform() const; void setThumbnailImage(const QImage &image, QTransform thumbToImageTransform); public: virtual bool acceptsClicks() const; virtual void paint(QPainter &gc) = 0; virtual QCursor getCurrentCursor() const = 0; virtual QPainterPath getCursorOutline() const; virtual void externalConfigChanged() = 0; + virtual void activatePrimaryAction(); + virtual void deactivatePrimaryAction(); + virtual bool beginPrimaryAction(KoPointerEvent *event) = 0; virtual void continuePrimaryAction(KoPointerEvent *event) = 0; virtual bool endPrimaryAction(KoPointerEvent *event) = 0; virtual void hoverActionCommon(KoPointerEvent *event) = 0; virtual void activateAlternateAction(KisTool::AlternateAction action); virtual void deactivateAlternateAction(KisTool::AlternateAction action); virtual bool beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); virtual void continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); virtual bool endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_TRANSFORM_STRATEGY_BASE_H */ diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp index 25f062d88e..3537d75f53 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.cpp +++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp @@ -1,368 +1,391 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transform_utils.h" #include #include #include #include "tool_transform_args.h" #include "kis_paint_device.h" const int KisTransformUtils::rotationHandleVisualRadius = 12; const int KisTransformUtils::rotationHandleRadius = 8; const int KisTransformUtils::handleVisualRadius = 12; const int KisTransformUtils::handleRadius = 8; QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter) { return converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); } qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) { return KoUnit::approxTransformScale(t); } qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(1.0, 0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(0, 1.0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::effectiveSize(const QRectF &rc) { return 0.5 * (rc.width() + rc.height()); } QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) { const qreal handlesExtraScaleX = KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint); const qreal handlesExtraScaleY = KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint); const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect); const qreal dX = qMin(maxD, radius / handlesExtraScaleX); const qreal dY = qMin(maxD, radius / handlesExtraScaleY); QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY); if (dOutX) { *dOutX = dX; } if (dOutY) { *dOutY = dY; } return handleRect; } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) { return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY); } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) { return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0); } QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r) { QPointF center = r.center(); QPointF t = p - center; r.translate(- center); if (t.y() != 0) { if (t.x() != 0) { double slope = t.y() / t.x(); if (t.x() < r.left()) { t.setY(r.left() * slope); t.setX(r.left()); } else if (t.x() > r.right()) { t.setY(r.right() * slope); t.setX(r.right()); } if (t.y() < r.top()) { t.setX(r.top() / slope); t.setY(r.top()); } else if (t.y() > r.bottom()) { t.setX(r.bottom() / slope); t.setY(r.bottom()); } } else { if (t.y() < r.top()) t.setY(r.top()); else if (t.y() > r.bottom()) t.setY(r.bottom()); } } else { if (t.x() < r.left()) t.setX(r.left()); else if (t.x() > r.right()) t.setX(r.right()); } t += center; return t; } KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args) { TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y()); SC = QTransform::fromScale(args.scaleX(), args.scaleY()); S.shear(0, args.shearY()); S.shear(args.shearX(), 0); if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) { P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0)); P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0)); P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1)); projectedP = P.toTransform(args.cameraPos().z()); } else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { projectedP = args.flattenedPerspectiveTransform(); P = QMatrix4x4(projectedP); } QPointF translation = args.transformedCenter(); T = QTransform::fromTranslate(translation.x(), translation.y()); } QTransform KisTransformUtils::MatricesPack::finalTransform() const { return TS * SC * S * projectedP * T; } bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m) { bool imageTooBig = false; QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S); QVector points; points << bounds.topLeft(); points << bounds.topRight(); points << bounds.bottomRight(); points << bounds.bottomLeft(); Q_FOREACH (const QPointF &pt, points) { QVector4D v(pt.x(), pt.y(), 0, 1); v = unprojectedMatrix * v; qreal z = v.z() / v.w(); imageTooBig = z > 1024.0; if (imageTooBig) { break; } } return imageTooBig; } #include #include #include #include #include KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */) { { KisTransformWorker t(0, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), 0, // set X and Y translation 0, // to null for calculation 0, config.filter()); *transformedCenter = QVector3D(t.transform().map(config.originalCenter())); } QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF(); KisTransformWorker transformWorker(device, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), (int)(translation.x()), (int)(translation.y()), updater, config.filter()); return transformWorker; } void KisTransformUtils::transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper) { if (config.mode() == ToolTransformArgs::WARP) { KoUpdaterPtr updater = helper->updater(); KisWarpTransformWorker worker(config.warpType(), device, config.origPoints(), config.transfPoints(), config.alpha(), updater); worker.run(); } else if (config.mode() == ToolTransformArgs::CAGE) { KoUpdaterPtr updater = helper->updater(); KisCageTransformWorker worker(device, config.origPoints(), updater, 8); worker.prepareTransform(); worker.setTransformedCage(config.transfPoints()); worker.run(); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { KoUpdaterPtr updater = helper->updater(); //FIXME: Q_UNUSED(updater); config.liquifyWorker()->run(device); } else { QVector3D transformedCenter; KoUpdaterPtr updater1 = helper->updater(); KoUpdaterPtr updater2 = helper->updater(); KisTransformWorker transformWorker = createTransformWorker(config, device, updater1, &transformedCenter); transformWorker.run(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) { KisPerspectiveTransformWorker perspectiveWorker(device, config.transformedCenter(), config.aX(), config.aY(), config.cameraPos().z(), updater2); perspectiveWorker.run(); } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { QTransform T = QTransform::fromTranslate(config.transformedCenter().x(), config.transformedCenter().y()); KisPerspectiveTransformWorker perspectiveWorker(device, T.inverted() * config.flattenedPerspectiveTransform() * T, updater2); perspectiveWorker.run(); } } } QRect KisTransformUtils::needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, 8); worker.setTransformedCage(config.transfPoints()); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } QRect KisTransformUtils::changeRect(const ToolTransformArgs &config, const QRect &rc) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, 8); worker.setTransformedCage(config.transfPoints()); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxChangeRect(rc) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } + +KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config) + : m_enabled(enabled), + m_config(config) +{ + if (!m_enabled) return; + + m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset(); + + const KisTransformUtils::MatricesPack m(*m_config); + m_oldStaticPointInView = m.finalTransform().map(m_staticPoint); +} + +KisTransformUtils::AnchorHolder::~AnchorHolder() { + if (!m_enabled) return; + + const KisTransformUtils::MatricesPack m(*m_config); + const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint); + + const QPointF diff = m_oldStaticPointInView - newStaticPointInView; + + m_config->setTransformedCenter(m_config->transformedCenter() + diff); +} diff --git a/plugins/tools/tool_transform2/kis_transform_utils.h b/plugins/tools/tool_transform2/kis_transform_utils.h index 340f12e673..9e84f29269 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.h +++ b/plugins/tools/tool_transform2/kis_transform_utils.h @@ -1,139 +1,156 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TRANSFORM_UTILS_H #define __KIS_TRANSFORM_UTILS_H #include #include "kis_coordinates_converter.h" #include #include #include #include // for kisSquareDistance only #include "kis_global.h" class ToolTransformArgs; class KisTransformWorker; class KisTransformUtils { public: static const int rotationHandleVisualRadius; static const int handleVisualRadius; static const int handleRadius; static const int rotationHandleRadius; template static T flakeToImage(const KisCoordinatesConverter *converter, T object) { return converter->documentToImage(converter->flakeToDocument(object)); } template static T imageToFlake(const KisCoordinatesConverter *converter, T object) { return converter->documentToFlake(converter->imageToDocument(object)); } static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter); static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter); static qreal effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter); static qreal scaleFromAffineMatrix(const QTransform &t); static qreal scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt); static qreal scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt); static qreal effectiveSize(const QRectF &rc); static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY); static QRectF handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint); static QPointF clipInRect(QPointF p, QRectF r); struct MatricesPack { MatricesPack(const ToolTransformArgs &args); QTransform TS; QTransform SC; QTransform S; QMatrix4x4 P; QTransform projectedP; QTransform T; // the final transformation looks like // transform = TS * SC * S * projectedP * T QTransform finalTransform() const; }; static bool checkImageTooBig(const QRectF &bounds, const MatricesPack &m); static KisTransformWorker createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */); static void transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper); static QRect needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds); static QRect changeRect(const ToolTransformArgs &config, const QRect &rc); template class HandleChooser { public: HandleChooser(const QPointF &cursorPos, Function defaultFunction) : m_cursorPos(cursorPos), m_minDistance(std::numeric_limits::max()), m_function(defaultFunction) { } bool addFunction(const QPointF &pt, qreal radius, Function function) { bool result = false; qreal distance = kisSquareDistance(pt, m_cursorPos); if (distance < pow2(radius) && distance < m_minDistance) { m_minDistance = distance; m_function = function; result = true; } return result; } Function function() const { return m_function; } private: QPointF m_cursorPos; qreal m_minDistance; Function m_function; }; + + /** + * A special class that ensures that the view position of the anchor point of the + * transformation is unchanged during the lifetime of the object. On destruction + * of the keeper the position of the anchor point will be restored. + */ + struct AnchorHolder { + AnchorHolder(bool enabled, ToolTransformArgs *config); + ~AnchorHolder(); + + private: + bool m_enabled; + ToolTransformArgs *m_config; + QPointF m_staticPoint; + QPointF m_oldStaticPointInView; + + }; }; #endif /* __KIS_TRANSFORM_UTILS_H */ diff --git a/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp index 4be3e2edd3..26b70b422b 100644 --- a/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp @@ -1,590 +1,591 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_warp_transform_strategy.h" #include #include #include #include "kis_coordinates_converter.h" #include "tool_transform_args.h" #include "transform_transaction_properties.h" #include "krita_utils.h" #include "kis_cursor.h" #include "kis_transform_utils.h" #include "kis_algebra_2d.h" struct KisWarpTransformStrategy::Private { Private(KisWarpTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction) : q(_q), converter(_converter), currentArgs(_currentArgs), transaction(_transaction), lastNumPoints(0), drawConnectionLines(true), drawOrigPoints(true), drawTransfPoints(true), closeOnStartPointClick(false), clipOriginalPointsPosition(true), pointWasDragged(false) { } KisWarpTransformStrategy * const q; /// standard members /// const KisCoordinatesConverter *converter; ////// ToolTransformArgs ¤tArgs; ////// TransformTransactionProperties &transaction; QTransform paintingTransform; QPointF paintingOffset; QTransform handlesTransform; /// custom members /// QImage transformedImage; int pointIndexUnderCursor; enum Mode { OVER_POINT = 0, MULTIPLE_POINT_SELECTION, MOVE_MODE, ROTATE_MODE, SCALE_MODE, NOTHING }; Mode mode; QVector pointsInAction; int lastNumPoints; bool drawConnectionLines; bool drawOrigPoints; bool drawTransfPoints; bool closeOnStartPointClick; bool clipOriginalPointsPosition; QPointF pointPosOnClick; bool pointWasDragged; QPointF lastMousePos; void recalculateTransformations(); inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization); bool shouldCloseTheCage() const; QVector getSelectedPoints(QPointF *center, bool limitToSelectedOnly = false) const; }; KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction) : KisSimplifiedActionPolicyStrategy(converter), m_d(new Private(this, converter, currentArgs, transaction)) { } KisWarpTransformStrategy::~KisWarpTransformStrategy() { } void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) { double handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter); bool cursorOverPoint = false; m_d->pointIndexUnderCursor = -1; KisTransformUtils::HandleChooser handleChooser(mousePos, Private::NOTHING); const QVector &points = m_d->currentArgs.transfPoints(); for (int i = 0; i < points.size(); ++i) { if (handleChooser.addFunction(points[i], handleRadius, Private::NOTHING)) { cursorOverPoint = true; m_d->pointIndexUnderCursor = i; } } if (cursorOverPoint) { m_d->mode = perspectiveModifierActive && !m_d->currentArgs.isEditingTransformPoints() ? Private::MULTIPLE_POINT_SELECTION : Private::OVER_POINT; } else if (!m_d->currentArgs.isEditingTransformPoints()) { QPolygonF polygon(m_d->currentArgs.transfPoints()); bool insidePolygon = polygon.boundingRect().contains(mousePos); m_d->mode = insidePolygon ? Private::MOVE_MODE : !perspectiveModifierActive ? Private::ROTATE_MODE : Private::SCALE_MODE; } else { m_d->mode = Private::NOTHING; } } QCursor KisWarpTransformStrategy::getCurrentCursor() const { QCursor cursor; switch (m_d->mode) { case Private::OVER_POINT: cursor = KisCursor::pointingHandCursor(); break; case Private::MULTIPLE_POINT_SELECTION: cursor = KisCursor::crossCursor(); break; case Private::MOVE_MODE: cursor = KisCursor::moveCursor(); break; case Private::ROTATE_MODE: cursor = KisCursor::rotateCursor(); break; case Private::SCALE_MODE: cursor = KisCursor::sizeVerCursor(); break; case Private::NOTHING: cursor = KisCursor::arrowCursor(); break; } return cursor; } void KisWarpTransformStrategy::overrideDrawingItems(bool drawConnectionLines, bool drawOrigPoints, bool drawTransfPoints) { m_d->drawConnectionLines = drawConnectionLines; m_d->drawOrigPoints = drawOrigPoints; m_d->drawTransfPoints = drawTransfPoints; } void KisWarpTransformStrategy::setCloseOnStartPointClick(bool value) { m_d->closeOnStartPointClick = value; } void KisWarpTransformStrategy::setClipOriginalPointsPosition(bool value) { m_d->clipOriginalPointsPosition = value; } void KisWarpTransformStrategy::drawConnectionLines(QPainter &gc, const QVector &origPoints, const QVector &transfPoints, bool isEditingPoints) { Q_UNUSED(isEditingPoints); QPen antsPen; QPen outlinePen; KritaUtils::initAntsPen(&antsPen, &outlinePen); const int numPoints = origPoints.size(); for (int i = 0; i < numPoints; ++i) { gc.setPen(outlinePen); gc.drawLine(transfPoints[i], origPoints[i]); gc.setPen(antsPen); gc.drawLine(transfPoints[i], origPoints[i]); } } void KisWarpTransformStrategy::paint(QPainter &gc) { // Draw preview image gc.save(); gc.setOpacity(m_d->transaction.basePreviewOpacity()); gc.setTransform(m_d->paintingTransform, true); gc.drawImage(m_d->paintingOffset, m_d->transformedImage); gc.restore(); gc.save(); gc.setTransform(m_d->handlesTransform, true); // draw connecting lines if (m_d->drawConnectionLines) { gc.setOpacity(0.5); drawConnectionLines(gc, m_d->currentArgs.origPoints(), m_d->currentArgs.transfPoints(), m_d->currentArgs.isEditingTransformPoints()); } // draw handles { const int numPoints = m_d->currentArgs.origPoints().size(); QPen mainPen(Qt::black); QPen outlinePen(Qt::white); qreal handlesExtraScale = KisTransformUtils::scaleFromAffineMatrix(m_d->handlesTransform); qreal dstIn = 8 / handlesExtraScale; qreal dstOut = 10 / handlesExtraScale; qreal srcIn = 6 / handlesExtraScale; qreal srcOut = 6 / handlesExtraScale; QRectF handleRect1(-0.5 * dstIn, -0.5 * dstIn, dstIn, dstIn); QRectF handleRect2(-0.5 * dstOut, -0.5 * dstOut, dstOut, dstOut); if (m_d->drawTransfPoints) { gc.setOpacity(1.0); for (int i = 0; i < numPoints; ++i) { gc.setPen(outlinePen); gc.drawEllipse(handleRect2.translated(m_d->currentArgs.transfPoints()[i])); gc.setPen(mainPen); gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i])); } QPointF center; QVector selectedPoints = m_d->getSelectedPoints(¢er, true); QBrush selectionBrush = selectedPoints.size() > 1 ? Qt::red : Qt::black; QBrush oldBrush = gc.brush(); gc.setBrush(selectionBrush); Q_FOREACH (const QPointF *pt, selectedPoints) { gc.drawEllipse(handleRect1.translated(*pt)); } gc.setBrush(oldBrush); } if (m_d->drawOrigPoints) { QPainterPath inLine; inLine.moveTo(-0.5 * srcIn, 0); inLine.lineTo( 0.5 * srcIn, 0); inLine.moveTo( 0, -0.5 * srcIn); inLine.lineTo( 0, 0.5 * srcIn); QPainterPath outLine; outLine.moveTo(-0.5 * srcOut, -0.5 * srcOut); outLine.lineTo( 0.5 * srcOut, -0.5 * srcOut); outLine.lineTo( 0.5 * srcOut, 0.5 * srcOut); outLine.lineTo(-0.5 * srcOut, 0.5 * srcOut); outLine.lineTo(-0.5 * srcOut, -0.5 * srcOut); gc.setOpacity(0.5); for (int i = 0; i < numPoints; ++i) { gc.setPen(outlinePen); gc.drawPath(outLine.translated(m_d->currentArgs.origPoints()[i])); gc.setPen(mainPen); gc.drawPath(inLine.translated(m_d->currentArgs.origPoints()[i])); } } } gc.restore(); } void KisWarpTransformStrategy::externalConfigChanged() { if (m_d->lastNumPoints != m_d->currentArgs.transfPoints().size()) { m_d->pointsInAction.clear(); } m_d->recalculateTransformations(); } bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt) { const bool isEditingPoints = m_d->currentArgs.isEditingTransformPoints(); bool retval = false; if (m_d->mode == Private::OVER_POINT || m_d->mode == Private::MULTIPLE_POINT_SELECTION || m_d->mode == Private::MOVE_MODE || m_d->mode == Private::ROTATE_MODE || m_d->mode == Private::SCALE_MODE) { retval = true; } else if (isEditingPoints) { QPointF newPos = m_d->clipOriginalPointsPosition ? KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) : pt; m_d->currentArgs.refOriginalPoints().append(newPos); m_d->currentArgs.refTransformedPoints().append(newPos); m_d->mode = Private::OVER_POINT; m_d->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1; m_d->recalculateTransformations(); emit requestCanvasUpdate(); retval = true; } if (m_d->mode == Private::OVER_POINT) { m_d->pointPosOnClick = m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor]; m_d->pointWasDragged = false; m_d->pointsInAction.clear(); m_d->pointsInAction << m_d->pointIndexUnderCursor; m_d->lastNumPoints = m_d->currentArgs.transfPoints().size(); } else if (m_d->mode == Private::MULTIPLE_POINT_SELECTION) { QVector::iterator it = std::find(m_d->pointsInAction.begin(), m_d->pointsInAction.end(), m_d->pointIndexUnderCursor); if (it != m_d->pointsInAction.end()) { m_d->pointsInAction.erase(it); } else { m_d->pointsInAction << m_d->pointIndexUnderCursor; } m_d->lastNumPoints = m_d->currentArgs.transfPoints().size(); } m_d->lastMousePos = pt; return retval; } QVector KisWarpTransformStrategy::Private::getSelectedPoints(QPointF *center, bool limitToSelectedOnly) const { QVector &points = currentArgs.refTransformedPoints(); QRectF boundingRect; QVector selectedPoints; if (limitToSelectedOnly || pointsInAction.size() > 1) { Q_FOREACH (int index, pointsInAction) { selectedPoints << &points[index]; KisAlgebra2D::accumulateBounds(points[index], &boundingRect); } } else { QVector::iterator it = points.begin(); QVector::iterator end = points.end(); for (; it != end; ++it) { selectedPoints << &(*it); KisAlgebra2D::accumulateBounds(*it, &boundingRect); } } *center = boundingRect.center(); return selectedPoints; } -void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool specialModifierActve) +void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive) { - Q_UNUSED(specialModifierActve); + Q_UNUSED(shiftModifierActve); + Q_UNUSED(altModifierActive); // toplevel code switches to HOVER mode if nothing is selected KIS_ASSERT_RECOVER_RETURN(m_d->mode == Private::MOVE_MODE || m_d->mode == Private::ROTATE_MODE || m_d->mode == Private::SCALE_MODE || (m_d->mode == Private::OVER_POINT && m_d->pointIndexUnderCursor >= 0 && m_d->pointsInAction.size() == 1) || (m_d->mode == Private::MULTIPLE_POINT_SELECTION && m_d->pointIndexUnderCursor >= 0)); if (m_d->mode == Private::OVER_POINT) { if (m_d->currentArgs.isEditingTransformPoints()) { QPointF newPos = m_d->clipOriginalPointsPosition ? KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) : pt; m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos; m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos; } else { m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt; } const qreal handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter)); qreal dist = kisSquareDistance( m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor), m_d->pointPosOnClick); if (dist > handleRadiusSq) { m_d->pointWasDragged = true; } } else if (m_d->mode == Private::MOVE_MODE) { QPointF center; QVector selectedPoints = m_d->getSelectedPoints(¢er); QPointF diff = pt - m_d->lastMousePos; QVector::iterator it = selectedPoints.begin(); QVector::iterator end = selectedPoints.end(); for (; it != end; ++it) { **it += diff; } } else if (m_d->mode == Private::ROTATE_MODE) { QPointF center; QVector selectedPoints = m_d->getSelectedPoints(¢er); QPointF oldDirection = m_d->lastMousePos - center; QPointF newDirection = pt - center; qreal rotateAngle = KisAlgebra2D::angleBetweenVectors(oldDirection, newDirection); QTransform R; R.rotateRadians(rotateAngle); QTransform t = QTransform::fromTranslate(-center.x(), -center.y()) * R * QTransform::fromTranslate(center.x(), center.y()); QVector::iterator it = selectedPoints.begin(); QVector::iterator end = selectedPoints.end(); for (; it != end; ++it) { **it = t.map(**it); } } else if (m_d->mode == Private::SCALE_MODE) { QPointF center; QVector selectedPoints = m_d->getSelectedPoints(¢er); QPolygonF polygon(m_d->currentArgs.origPoints()); QSizeF maxSize = polygon.boundingRect().size(); qreal maxDimension = qMax(maxSize.width(), maxSize.height()); qreal scale = 1.0 - (pt - m_d->lastMousePos).y() / maxDimension; QTransform t = QTransform::fromTranslate(-center.x(), -center.y()) * QTransform::fromScale(scale, scale) * QTransform::fromTranslate(center.x(), center.y()); QVector::iterator it = selectedPoints.begin(); QVector::iterator end = selectedPoints.end(); for (; it != end; ++it) { **it = t.map(**it); } } m_d->lastMousePos = pt; m_d->recalculateTransformations(); emit requestCanvasUpdate(); } bool KisWarpTransformStrategy::Private::shouldCloseTheCage() const { return currentArgs.isEditingTransformPoints() && closeOnStartPointClick && pointIndexUnderCursor == 0 && currentArgs.origPoints().size() > 2 && !pointWasDragged; } bool KisWarpTransformStrategy::acceptsClicks() const { return m_d->shouldCloseTheCage() || m_d->currentArgs.isEditingTransformPoints(); } bool KisWarpTransformStrategy::endPrimaryAction() { if (m_d->shouldCloseTheCage()) { m_d->currentArgs.setEditingTransformPoints(false); } return true; } inline QPointF KisWarpTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization) { return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt); } void KisWarpTransformStrategy::Private::recalculateTransformations() { QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter); QTransform resultTransform = q->thumbToImageTransform() * scaleTransform; qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultTransform); bool useFlakeOptimization = scale < 1.0; QVector thumbOrigPoints(currentArgs.numPoints()); QVector thumbTransfPoints(currentArgs.numPoints()); for (int i = 0; i < currentArgs.numPoints(); ++i) { thumbOrigPoints[i] = imageToThumb(currentArgs.origPoints()[i], useFlakeOptimization); thumbTransfPoints[i] = imageToThumb(currentArgs.transfPoints()[i], useFlakeOptimization); } paintingOffset = transaction.originalTopLeft(); if (!q->originalImage().isNull() && !currentArgs.isEditingTransformPoints()) { QPointF origTLInFlake = imageToThumb(transaction.originalTopLeft(), useFlakeOptimization); if (useFlakeOptimization) { transformedImage = q->originalImage().transformed(q->thumbToImageTransform() * scaleTransform); paintingTransform = QTransform(); } else { transformedImage = q->originalImage(); paintingTransform = q->thumbToImageTransform() * scaleTransform; } transformedImage = q->calculateTransformedImage(currentArgs, transformedImage, thumbOrigPoints, thumbTransfPoints, origTLInFlake, &paintingOffset); } else { transformedImage = q->originalImage(); paintingOffset = imageToThumb(transaction.originalTopLeft(), false); paintingTransform = q->thumbToImageTransform() * scaleTransform; } handlesTransform = scaleTransform; } QImage KisWarpTransformStrategy::calculateTransformedImage(ToolTransformArgs ¤tArgs, const QImage &srcImage, const QVector &origPoints, const QVector &transfPoints, const QPointF &srcOffset, QPointF *dstOffset) { return KisWarpTransformWorker::transformQImage( currentArgs.warpType(), origPoints, transfPoints, currentArgs.alpha(), srcImage, srcOffset, dstOffset); } diff --git a/plugins/tools/tool_transform2/kis_warp_transform_strategy.h b/plugins/tools/tool_transform2/kis_warp_transform_strategy.h index 65792a8b8c..39d195ca81 100644 --- a/plugins/tools/tool_transform2/kis_warp_transform_strategy.h +++ b/plugins/tools/tool_transform2/kis_warp_transform_strategy.h @@ -1,91 +1,91 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_WARP_TRANSFORM_STRATEGY_H #define __KIS_WARP_TRANSFORM_STRATEGY_H #include #include #include "kis_simplified_action_policy_strategy.h" class QPointF; class QPainter; class KisCoordinatesConverter; class ToolTransformArgs; class TransformTransactionProperties; class QCursor; class QImage; class KisWarpTransformStrategy : public KisSimplifiedActionPolicyStrategy { Q_OBJECT public: KisWarpTransformStrategy(const KisCoordinatesConverter *converter, ToolTransformArgs ¤tArgs, TransformTransactionProperties &transaction); ~KisWarpTransformStrategy(); void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive); void paint(QPainter &gc); QCursor getCurrentCursor() const; void externalConfigChanged(); using KisTransformStrategyBase::beginPrimaryAction; using KisTransformStrategyBase::continuePrimaryAction; using KisTransformStrategyBase::endPrimaryAction; bool beginPrimaryAction(const QPointF &pt); - void continuePrimaryAction(const QPointF &pt, bool specialModifierActve); + void continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive); bool endPrimaryAction(); bool acceptsClicks() const; Q_SIGNALS: void requestCanvasUpdate(); protected: // default is true void setClipOriginalPointsPosition(bool value); // default is false void setCloseOnStartPointClick(bool value); void overrideDrawingItems(bool drawConnectionLines, bool drawOrigPoints, bool drawTransfPoints); virtual void drawConnectionLines(QPainter &gc, const QVector &origPoints, const QVector &transfPoints, bool isEditingPoints); virtual QImage calculateTransformedImage(ToolTransformArgs ¤tArgs, const QImage &srcImage, const QVector &origPoints, const QVector &transfPoints, const QPointF &srcOffset, QPointF *dstOffset); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_WARP_TRANSFORM_STRATEGY_H */ diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index 17dd30b30b..1a2612d47e 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,471 +1,486 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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 "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() : m_liquifyProperties(new KisLiquifyProperties()) { m_mode = FREE_TRANSFORM; m_transformedCenter = QPointF(0, 0); m_originalCenter = QPointF(0, 0); m_rotationCenterOffset = QPointF(0, 0); m_cameraPos = QVector3D(0,0,1024); m_aX = 0; m_aY = 0; m_aZ = 0; m_scaleX = 1.0; m_scaleY = 1.0; m_shearX = 0.0; m_shearY = 0.0; m_origPoints = QVector(); m_transfPoints = QVector(); m_warpType = KisWarpTransformWorker::RIGID_TRANSFORM; m_alpha = 1.0; m_keepAspectRatio = false; m_defaultPoints = true; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); + m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); m_editTransformPoints = false; } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } +void ToolTransformArgs::setTransformAroundRotationCenter(bool value) +{ + m_transformAroundRotationCenter = value; + + KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); + configGroup.writeEntry("transformAroundRotationCenter", int(value)); +} + void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); + m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) : m_liquifyProperties(args.m_liquifyProperties) { init(args); } ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { clear(); m_liquifyProperties = args.m_liquifyProperties; init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && + m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker); } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, + bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId) : m_liquifyProperties(new KisLiquifyProperties()) { m_mode = mode; m_transformedCenter = transformedCenter; m_originalCenter = originalCenter; m_rotationCenterOffset = rotationCenterOffset; + m_transformAroundRotationCenter = transformAroundRotationCenter; m_cameraPos = QVector3D(0,0,1024); m_aX = aX; m_aY = aY; m_aZ = aZ; m_scaleX = scaleX; m_scaleY = scaleY; m_shearX = shearX; m_shearY = shearY; m_origPoints = QVector(); m_transfPoints = QVector(); m_warpType = warpType; m_alpha = alpha; m_defaultPoints = defaultPoints; m_keepAspectRatio = false; setFilterId(filterId); m_editTransformPoints = false; } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { // Not implemented! return false; } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); + KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && + KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } if (!result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); } diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h index 26dc359a92..47b985ed0f 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.h +++ b/plugins/tools/tool_transform2/tool_transform_args.h @@ -1,318 +1,325 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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 TOOL_TRANSFORM_ARGS_H_ #define TOOL_TRANSFORM_ARGS_H_ #include #include #include #include #include "kis_liquify_properties.h" #include "kritatooltransform_export.h" #include "kis_global.h" #include class KisLiquifyTransformWorker; class QDomElement; /** * Class used to store the parameters of a transformation. * Some parameters are specific to free transform mode, and * others to warp mode : maybe add a union to save a little more * memory. */ class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs { public: enum TransformMode {FREE_TRANSFORM = 0, WARP, CAGE, LIQUIFY, PERSPECTIVE_4POINT, N_MODES}; /** * Initializes the parameters for an identity transformation, * with mode set to free transform. */ ToolTransformArgs(); /** * The object return will be a copy of args. */ ToolTransformArgs(const ToolTransformArgs& args); /** * If mode is warp, original and transformed vector points will be of size 0. * Use setPoints method to set those vectors. */ ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, - QPointF rotationCenterOffset, + QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId); ~ToolTransformArgs(); ToolTransformArgs& operator=(const ToolTransformArgs& args); bool operator==(const ToolTransformArgs& other) const; bool isSameMode(const ToolTransformArgs& other) const; inline TransformMode mode() const { return m_mode; } inline void setMode(TransformMode mode) { m_mode = mode; } //warp-related inline int numPoints() const { KIS_ASSERT_RECOVER_NOOP(m_origPoints.size() == m_transfPoints.size()); return m_origPoints.size(); } inline QPointF &origPoint(int i) { return m_origPoints[i]; } inline QPointF &transfPoint(int i) { return m_transfPoints[i]; } inline const QVector &origPoints() const { return m_origPoints; } inline const QVector &transfPoints() const { return m_transfPoints; } inline QVector &refOriginalPoints() { return m_origPoints; } inline QVector &refTransformedPoints() { return m_transfPoints; } inline KisWarpTransformWorker::WarpType warpType() const { return m_warpType; } inline double alpha() const { return m_alpha; } inline bool defaultPoints() const { return m_defaultPoints; } inline void setPoints(QVector origPoints, QVector transfPoints) { m_origPoints = QVector(origPoints); m_transfPoints = QVector(transfPoints); } inline void setWarpType(KisWarpTransformWorker::WarpType warpType) { m_warpType = warpType; } inline void setAlpha(double alpha) { m_alpha = alpha; } inline void setDefaultPoints(bool defaultPoints) { m_defaultPoints = defaultPoints; } //"free transform"-related inline QPointF transformedCenter() const { return m_transformedCenter; } inline QPointF originalCenter() const { return m_originalCenter; } inline QPointF rotationCenterOffset() const { return m_rotationCenterOffset; } + inline bool transformAroundRotationCenter() const { + return m_transformAroundRotationCenter; + } inline double aX() const { return m_aX; } inline double aY() const { return m_aY; } inline double aZ() const { return m_aZ; } inline QVector3D cameraPos() const { return m_cameraPos; } inline double scaleX() const { return m_scaleX; } inline double scaleY() const { return m_scaleY; } inline bool keepAspectRatio() const { return m_keepAspectRatio; } inline double shearX() const { return m_shearX; } inline double shearY() const { return m_shearY; } inline void setTransformedCenter(QPointF transformedCenter) { m_transformedCenter = transformedCenter; } inline void setOriginalCenter(QPointF originalCenter) { m_originalCenter = originalCenter; } inline void setRotationCenterOffset(QPointF rotationCenterOffset) { m_rotationCenterOffset = rotationCenterOffset; } + void setTransformAroundRotationCenter(bool value); inline void setAX(double aX) { KIS_ASSERT_RECOVER_NOOP(aX == normalizeAngle(aX)); m_aX = aX; } inline void setAY(double aY) { KIS_ASSERT_RECOVER_NOOP(aY == normalizeAngle(aY)); m_aY = aY; } inline void setAZ(double aZ) { KIS_ASSERT_RECOVER_NOOP(aZ == normalizeAngle(aZ)); m_aZ = aZ; } inline void setCameraPos(const QVector3D &pos) { m_cameraPos = pos; } inline void setScaleX(double scaleX) { m_scaleX = scaleX; } inline void setScaleY(double scaleY) { m_scaleY = scaleY; } inline void setKeepAspectRatio(bool value) { m_keepAspectRatio = value; } inline void setShearX(double shearX) { m_shearX = shearX; } inline void setShearY(double shearY) { m_shearY = shearY; } inline QString filterId() const { return m_filter->id(); } void setFilterId(const QString &id); inline KisFilterStrategy* filter() const { return m_filter; } bool isIdentity() const; inline QTransform flattenedPerspectiveTransform() const { return m_flattenedPerspectiveTransform; } inline void setFlattenedPerspectiveTransform(const QTransform &value) { m_flattenedPerspectiveTransform = value; } bool isEditingTransformPoints() const { return m_editTransformPoints; } void setEditingTransformPoints(bool value) { m_editTransformPoints = value; } const KisLiquifyProperties* liquifyProperties() const { return m_liquifyProperties.data(); } KisLiquifyProperties* liquifyProperties() { return m_liquifyProperties.data(); } void initLiquifyTransformMode(const QRect &srcRect); void saveLiquifyTransformMode() const; KisLiquifyTransformWorker* liquifyWorker() const { return m_liquifyWorker.data(); } void toXML(QDomElement *e) const; static ToolTransformArgs fromXML(const QDomElement &e); void translate(const QPointF &offset); void saveContinuedState(); void restoreContinuedState(); const ToolTransformArgs* continuedTransform() const; private: void clear(); void init(const ToolTransformArgs& args); TransformMode m_mode; // warp-related arguments // these are basically the arguments taken by the warp transform worker bool m_defaultPoints; // true : the original points are set to make a grid // which density is given by numPoints() QVector m_origPoints; QVector m_transfPoints; KisWarpTransformWorker::WarpType m_warpType; double m_alpha; //'free transform'-related // basically the arguments taken by the transform worker QPointF m_transformedCenter; QPointF m_originalCenter; QPointF m_rotationCenterOffset; // the position of the rotation center relative to // the original top left corner of the selection // before any transformation + bool m_transformAroundRotationCenter; // In freehand mode makes the scaling and other transformations + // be anchored to the rotation center point. + double m_aX; double m_aY; double m_aZ; QVector3D m_cameraPos; double m_scaleX; double m_scaleY; double m_shearX; double m_shearY; bool m_keepAspectRatio; // perspective trasform related QTransform m_flattenedPerspectiveTransform; KisFilterStrategy *m_filter; bool m_editTransformPoints; QSharedPointer m_liquifyProperties; QScopedPointer m_liquifyWorker; /** * When we continue a transformation, m_continuedTransformation * stores the initial step of our transform. All cancel and revert * operations should revert to it. */ QScopedPointer m_continuedTransformation; }; #endif // TOOL_TRANSFORM_ARGS_H_ diff --git a/plugins/tools/tool_transform2/wdg_tool_transform.ui b/plugins/tools/tool_transform2/wdg_tool_transform.ui index c46c98b77e..2ed7769bcc 100755 --- a/plugins/tools/tool_transform2/wdg_tool_transform.ui +++ b/plugins/tools/tool_transform2/wdg_tool_transform.ui @@ -1,2189 +1,2204 @@ WdgToolTransform 0 0 479 - 591 + 674 0 0 0 0 16777215 16777215 0 0 Qt::LeftToRight 0 0 0 0 0 4 QFrame::NoFrame QFrame::Plain 0 1 1 1 1 0 0 0 Free Free Transform true true true true 0 0 Perspective Perspective true true true 0 0 Warp Warp true false true true 0 0 Cage Cage true true true 0 0 Liquify Liquify true true true 0 0 Free Transform Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter QFrame::StyledPanel QFrame::Raised - 0 + 3 0 0 0 0 0 0 12 QLayout::SetFixedSize 10 10 20 10 15 0 0 Qt::RightToLeft &Filter: Qt::AlignCenter cmbFilter QLayout::SetDefaultConstraint 0 true 0 0 true true 0 0 true true 0 0 true true 0 0 true true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 0 + + + + + 0 + 0 + + + + Transform around pivot point (Alt) + + + + + + true + + + true + + + Posi&tion true freeTransformRadioGroup &Rotate freeTransformRadioGroup Scale freeTransformRadioGroup Shear freeTransformRadioGroup 0 0 0 0 75 true Qt::LeftToRight false off canvas Qt::RichText false true Qt::NoTextInteraction - + Qt::Horizontal 40 20 true 0 0 240 100 Rotation false false false 2 5 0 7 0 QFormLayout::AllNonFixedFieldsGrow 5 5 0 0 0 0 Rotate around X-Axis Qt::LeftToRight &x: aXBox Rotate around X-Axis 0 0 0 0 Rotate around Y-Axis Qt::LeftToRight &y: aYBox Rotate around Y-Axis 0 0 Rotate around Z-Axis 0 0 0 0 Rotate around Z-Axis Qt::LeftToRight &z: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter aZBox 0 0 240 70 Scale false false false 2 5 0 0 5 0 0 0 0 Horizontal Scale w&idth: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleXBox 0 0 Horizontal Scale % 0 0 Vertical Scale % 0 0 0 0 Vertical Scale &height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter scaleYBox 0 0 20 25 0 0 240 70 Position false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 0 0 Horizontal Translation Qt::LeftToRight x: translateXBox 0 0 Horizontal Translation Qt::LeftToRight 0 0 Vertical Translation 0 0 Vertical Translation y: translateYBox 0 0 240 80 false Shear Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false false false 2 5 0 0 QFormLayout::AllNonFixedFieldsGrow 8 20 0 0 Horizontal Shear x: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearXBox 0 0 Vertical Shear 0 0 0 0 Vertical Shear y: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter shearYBox 0 0 Horizontal Shear - + Qt::Vertical QSizePolicy::Preferred 20 - 20 + 10 - - - - 6 - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - Flip selection horizontally - - - - - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - Flip selection vertically - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 20 - 20 - - - - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - Rotate selection counter-clockwise 90 degrees - - - - - - - - - - 0 - 0 - - - - - 32 - 16777215 - - - - Rotate the selection clockwise 90 degrees - - - + + + + + 6 - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 20 - 20 - - - - - - + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Flip selection horizontally + + + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Flip selection vertically + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Rotate selection counter-clockwise 90 degrees + + + + + + + + + + + 0 + 0 + + + + + 32 + 16777215 + + + + Rotate the selection clockwise 90 degrees + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + - + Qt::Vertical QSizePolicy::Preferred 20 - 20 + 10 - - - - 10 - 0 - 251 - 51 - - - - - 10 - - - 0 - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - - 2 - 0 - - - - &Flexibility: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - alphaBox - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Anc&hor Strength: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - cmbWarpType - - - - - - - - - 10 - 70 - 260 - 111 - - - - false - - - Anchor Points - - - false - - - false - - - - - 10 - 30 - 241 - 29 - - - - - 0 - - - 0 - - - 0 - - - 0 + + + + + 10 - + 0 - - + + - + 0 0 - - Subd&ivide + + + + + + + 0 + 0 + - - true + + + 2 + 0 + - - true + + &Flexibility: - - buttonGroup - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - 0 + + alphaBox - - 3 + + + + + + + 0 + 0 + - - - - - - 10 - 60 - 290 - 47 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - + + 0 0 - Draw + Anc&hor Strength: - - true + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + cmbWarpType - - buttonGroup - - - - - false - - + + + + + + false + + + Anchor Points + + + false + + + false + + + + 0 0 0 0 0 - + + + + 0 + 0 + + - Clear Points + Subd&ivide + + + true + + true + + + buttonGroup + - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0 + + + 3 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + - Lock Points + Draw + + true + + + buttonGroup + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Clear Points + + + + + + + Lock Points + + + + - - - - - +
+ + +
+ + + + Qt::Vertical + + + + 20 + 10 + + + + + Create 3 points on the canvas to begin Add/Ed&it Anchor Points true cageTransformButtonGroup true De&form Layer cageTransformButtonGroup Qt::Vertical 20 40 0 0 0 <html><head/><body><p><br/></p></body></html> QFrame::NoFrame QFrame::Plain 0 2 2 15 0 0 Move true true true true 0 0 Scale true true true 0 0 Rotate true true true 0 0 Offset true true true 0 0 Undo true true true 3 5 0 0 51 0 Reverse: 0 0 49 0 Spacing: 0 0 32 0 Flow: 0 0 27 0 Size: 0 0 51 0 Amount: 0 0 38 0 Mode: 0 0 0 Build Up Wash true 0 0 true Pressure true true true Pressure true true true Qt::Vertical 20 - 40 + 0
6 6 0 0 32 16777215 Show Decorations true false 0 0 32 16777215 Work Recursively true true Qt::Horizontal 13 13 Qt::LeftToRight Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Reset true KisCmbIDList
widgets/kis_cmb_idlist.h
KoAspectButton QWidget
KoAspectButton.h
1
KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
- - + +